How can i execute a shell (bash) command within a Common Lisp program and assign the output to a variable?
ITA has released inferior-shell under their QITAB umbrella project.
Some links of possible interest :
http://common-lisp.net/gitweb?p=projects/qitab/inferior-shell.git
http://common-lisp.net/projects/qitab/
http://cliki.net/inferior-shell
A git repository is currently hosted at common-lisp.net :
git clone git://common-lisp.net/projects/qitab/inferior-shell.git
ASDF provides a RUN-SHELL-COMMAND that works with many Common Lisp implementations including ABCL, Allegro CL, CLISP, Clozure CL, ECL, GCL, LispWorks, SBCL, CMU, XCL and SCL.
It takes a control string and a list of arguments like FORMAT, and synchronously executes the result using a Bourne-compatible shell. Capture output by binding an optional stream.
You can consider using Trivial-shell (url)
(trivial-shell:shell-command "echo foo")
shell-command returns output, so you can assign it to a variable.
In asdf.lisp file you can read:
;;;; We probably should move this functionality to its own system and deprecate
;;;; use of it from the asdf package. However, this would break unspecified
;;;; existing software, so until a clear alternative exists, we can't deprecate
;;;; it, and even after it's been deprecated, we will support it for a few
;;;; years so everyone has time to migrate away from it. -- fare 2009-12-01
Nowadays I would use uiop:run-program, where uiop stands for "universal input output" and is a compatibility layer provided by asdf3, formerly known as asdf/driver. As has been said asdf:run-shell-command is obsolete and uiop inherits many features of other libraries such as trivial-shell.
UIOP readme
In sbcl:
(sb-ext:run-program "/bin/sh" (list "-c" "whoami") :input nil :output *standard-output*)
It works fine for me:)
Some CL implementations have built-in functions for this purpose. For example, SBCL has sb-ext:run-program, and CCL has run-program.
This (appupdate.cl) program is an example of creating and executing a shell script using the Steel Bank Common Lisp (sbcl) implementation, which assumes you have sbcl installed and its in your path.
I wrote this on Ubuntu 14.04 as a simple way to perform the automation of the updating, upgrading, and kernel upgrading of the app/system software.
#!/usr/local/bin/sbcl --script
(with-open-file (str "/home/geo/update.sh"
:direction :output
:if-exists :supersede
:if-does-not-exist :create)
(format str "#! /bin/bash~%~%apt-get update~%~%apt-get upgrade -y~%~%apt-get dist-upgrade -y~%~%exit~%))
(sb-ext:run-program "/bin/chmod" '("+x" "/home/geo/update.sh")
:output *standard-output*)
(sb-ext:run-program "/bin/bash" '("/home/geo/update.sh")
:output *standard-output*)
(sb-ext:run-program "/bin/rm" '("-rf" "/home/geo/update.sh")
:output *standard-output*)
So of course it creates a shell script entitled update.sh, which is directed to /bin/bash via shebang (#!). After doing so the sb-ext:run-program built directs a shell to execute /bin/chmod passing the flag "+x" as an argument and the /path/to/the-file. This function changes the mode of access of the file to executable (changes the permissions).
Next, a shell is open and executes /bin/bash and the bash binary is passed the argument of the executable shell scripts file location.
Lastly the file is removed from the working directory (note in this case the appupdate.cl is in my home directory therefore is the working directory).
The appupdate.cl file can be executed from the command line after it is changed to executable and temporary root privileges are gained:
:~$ chmod +x appupdate.cl
:~$ sudo bash
:~# ./appupdate.cl
:~# exit
Easily enough the sudo command could be added to the script (e.g. sudo apt-get update) and using the sudo bash sequence would not be necessary.
NOTE: In the LispWorks ide on 14.04 the (sys:run-shell-command "") is still applicable even though it has sort of become a 'legacy' function.
I tried out some answers but it was not straightforward.
This is what worked easily:
(ql:quickload "external-program")
;; run shell command e.g. "ls -l" and capture the output into string *output*
(defparameter *output*
(with-output-to-string (out)
(external-program:run "ls" '("-l") ; command with parameters as list of strings
:output out)))
;; and after that, you can write functions to parse the output ...
This is from Edi Weitz's book Common Lisp Recipes which belongs to the shelve of any serious Lisp programmer, in my view...
Related
This question already has answers here:
How to obtain the first letter in a Bash variable?
(7 answers)
Closed 3 years ago.
I am trying to my a custom terminal command. I just learned I am supposed to do it using the Unix script? I don't really know much of what that is and am still trying to figure it out. What I do know is that $1 is an arg is it possible to make it a variable and then get the first letter like you could in python?
EX:
str = 'happy'
str[0] = 'h'
You're asking a few different things here.
I am trying to my a custom terminal command.
That could mean a few different things, but the most obvious meaning is that you want to add an executable to your path so that when you type it at the terminal, it runs just like any other executable on your system. This requires just a few things:
the executable permission must be set.
the file must specify how it can be executed. For interpreted programs such as bash scripts or python scripts, you can do so by beginning the file with a "shebang line" that specifies the interpreter for the file.
the file must be in one of the locations specified by your $PATH.
I just learned I am supposed to do it using the Unix script?
there's no such thing as a "unix script", but what you seem to be referring to is a "shell script". Though these are commonly associated with unix, they're no more inherently a unix script than any other language. A shell, such as bash, sh, or any other, is just an interpreted language that is designed so that it is convenient to be used interactively by a human as well as being programmatically executed as part of a saved file.
I don't really know much of what that is and am still trying to figure it out.
Let's get into some specifics.
First I edit a file called 'hello-world' to contain:
#!/bin/bash
echo "Hello, world!"
Note that this filename has no "extension". Though heuristics based on file extension are sometimes used (espeically in windows) to determine a file type, unix typically sees a file "extension" as part of the arbitrary file name. The thing that makes this a potentially executable bash script is the specification of that interpreter on the shebang line.
We can run our script right now from bash, just as we could if we wrote a python script.
$ bash hello-world
hello, world!
To make the bash implicit, we mark the file as executable. This enables the linux operating system to consult the beginning "magic bytes" of the file to determine how to run it. Thes beginning bytes might signify an ELF file (a compiled executable, written in eg C, C++, or go). Or, it might be #! which just so happens means , "read the rest of this first line to determine the command to run, and pass the rest of this file into that command to be interpreted.
$ chmod +x hello-world
ls -l will show us the "permissions" on the file (more accurately called the "file mode", hence chmod rather than chperm) . The x stands for executable, so we have enabled the use of the leading bytes to determine method of execution. Remember, the first two bytes of this file, and the rest of that first line, then specify that this file should be "run through bash" so to speak.
$ ls -l hello-world
-rwxr-xr-x 1 danfarrell staff 33 Dec 27 20:02 hello-world
Now we can run the file from the current directory:
$ ./hello-world
hello, world!
At this point, the only difference between this command and any other on the system, is that you have to specify its location. That's because my current directory is not in the system path. In short, the path (accessible in a unix shell via the $PATH variable) specifies an ordered list of locations that should be searched for a specified command whose location is not otherwise specified.
For example, there's a very common program called whoami. I can run it directly from my terminal without specifying a location of the executable:
$ whoami
danfarrell
This is because there's a location in my $PATH in which the shell was able to find that command. Let's take a closer look. First, here's my path:
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
And there's also a convenient program called whereis which can help show which path elements supply a named executable:
$ whereis whoami
/usr/bin/whoami
Sure enough, whoami is in one of the elements of the $PATH. (Actually I shared a simplified $PATH. Yours might be somewhat longer).
Finally, then, we can get to the last thing. If I put hello-world in one of the $PATH elements, I will be able to invoke it without a path. There are two ways to do this: we can move the executable to a location specified in the path, or we can add a new location to the path. For simplicity's sake I'll choose the first of these.
$ sudo cp hello-world /usr/local/bin/
Password:
I needed to use sudo to write to /usr/local/bin because it's not accessible as my user directly - that's quite standard.
Finally, I've achieved the goal of being able to run my very important program from any location, without specifying the executable's location.
$ hello-world
hello, world!
$ which hello-world
/usr/local/bin/hello-world
It works! I've created what might be described as a "custom terminal command".
What I do know is that $1 is an arg is it possible to make it a variable and then get the first letter like you could in python?
Well, one option would be to simply write the custom terminal command in python. If python is available,
$ which python
/usr/bin/python
You can specify it in a shebang just like a shell can be:
#!/usr/bin/env python
print("hello, world!"[0])
$ hello-world
h
it works!
Okay, confession time. I actually used #!/usr/bin/env python, not /usr/bin/python. env helps find the correct python to use in the user's environment, rather than hard coding one particular python. If you've been using python during the very long running python 2 to python 3 migration, you can no doubt understand why I"m reticent to hard code a python executable in my program.
It's certainly possible to get the first letter of a string in a bash script. But it's also very possible to write a custom command in a program other than shell. Python is an excellent choice for string manipulation, if you know it. I often use python for shell one-liners that need to interact with json, a format that doesn't lend itself well to standard unix tool stream editing.
Anyway, at the expense of incurring SO community's ire by reanswering an "already answered" question, I'll include a version in shell (Credit goes to David C Rankin)
#!/bin/bash
echo "${1:0:1}"
$ hello-world hiworld
h
To start my Emacs Lisp script from shell, I use this command:
emacs --script my-script.el -f my-function
In my script I save 3 buffers to 3 files.
And it's working OK. But when script is running on the shell it prints the text:
Using vacuous schema
Saving file "some-file-to-save"
Wrote "some-file-to-save"
This text prints 3 times. How I can suppress this text?
I don't know offhand how to fix it "properly" in ELisp, but an easy solution would be to just discard output:
emacs --script my-script.el -f my-function > /dev/null
This tells the shell to send all of stdout to /dev/null (which discards all data written to it). Obviously this requires a) an operating system that has /dev/null (i.e. most Unices, including macOS) and b) a shell that doesn't suck (i.e. not cmd.exe).
Identify the function calls that dumps these messages in your script.
Assuming that the first message comes from the function save-buffer which calls the function message (files.el), just override the behavior of message by replacing, in your script, the invocation of save-buffer (or whoever is called) by:
(cl-letf (((symbol-function 'message) #'ignore))
(save-buffer))
Your need to add
(require 'cl-lib)
on top of your script if not already there.
This question already has answers here:
Running a Common Lisp function from a Terminal command prompt
(4 answers)
Closed 8 years ago.
I'm trying to run an old tool written in Lisp at the lisp REPL. I can run it every time by opening the lisp REPL, (load ...)ing the Lisp source for the tool and then running the tool command to open a file: (doStuff "filename" t). At which point it will start interactively asking me for input as it runs.
What I'd like to be able to do is to write some script which loads the tool, opens the file (which is an argument to the script (doStuff "$1" t)) and then goes into interactive mode.
As far as my research and experimentation goes, there is no way in the Lisp I am using (SBCL 1.0.57) to run a file and then go interactive. I can run input that I redirect/pipe into lisp, but the file whole process is non-interactive once I replace standard in with a redirect (correct me if I'm wrong, but that's what a pipe does as far as I understand).
What I would like is to somehow provide the Lisp REPL with several lines to run, and then have it start taking input from me. Then I can put this in a script that takes one argument (the file to use) and runs the command, prompting me for input when necessary.
Also, this does NOT need to be cross-platform or nice to look at. I'll be happy with any hacky CLI fu that works.
You use the command line option --load which is documented in 2.3.2 Toplevel Options:
--load filename
This is equivalent to --eval '(load "filename")'. The special syntax
is intended to reduce quoting headaches when invoking SBCL from shell
scripts.
For instance:
$ cat hello.lisp
(print "Starting with a special startup script!")
$ sbcl --load hello.lisp
This is SBCL 1.0.49, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
"Starting with a special startup script!"
* (print 'at-the-repl)
AT-THE-REPL
AT-THE-REPL
Bash commands are available from an interactive tclsh session. E.g. in a tclsh session you can have
% ls
instead of
$ exec ls
However, you cant have a tcl script which calls bash commands directly (i.e. without exec).
How can I make tclsh to recognize bash commands while interpreting tcl script files, just like it does in an interactive session?
I guess there is some tcl package (or something like that), which is being loaded automatically while launching an interactive session to support direct calls of bash commans. How can I load it manually in tcl script files?
If you want to have specific utilities available in your scripts, write bridging procedures:
proc ls args {
exec {*}[auto_execok ls] {*}$args
}
That will even work (with obvious adaptation) for most shell builtins or on Windows. (To be fair, you usually don't want to use an external ls; the internal glob command usually suffices, sometimes with extra help from some file subcommands.) Some commands need a little more work (e.g., redirecting input so it comes from the terminal, with an extra <#stdin or </dev/tty; that's needed for stty on some platforms) but that works reasonably well.
However, if what you're asking for is to have arbitrary execution of external programs without any extra code to mark that they are external, that's considered to be against the ethos of Tcl. The issue is that it makes the code quite a lot harder to maintain; it's not obvious that you're doing an expensive call-out instead of using something (relatively) cheap that's internal. Putting in the exec in that case isn't that onerous…
What's going on here is that the unknown proc is getting invoked when you type a command like ls, because that's not an existing tcl command, and by default, that command will check that if the command was invoked from an interactive session and from the top-level (not indirectly in a proc body) and it's checking to see if the proc name exists somewhere on the path. You can get something like this by writing your own proc unknown.
For a good start on this, examine the output of
info body unknown
One thing you should know is that ls is not a Bash command. It's a standalone utility. The clue for how tclsh runs such utilities is right there in its name - sh means "shell". So it's the rough equivalent to Bash in that Bash is also a shell. Tcl != tclsh so you have to use exec.
I want to type something like 'scheme file.scm' and have it interpret the file, and then take me back to my shell, rather than loading it in the REPL.
edit: I tried scheme < test.scm and it still uses the REPL, the only difference is that scheme exits when the stream ends.
scheme < file.scm should work (as long as you don't specify --interactive and stdin is not a terminal, scheme works non-interactively).
To run a scheme program using MIT Scheme:
scheme --quiet < program.scm
The --quiet option ensures that the output from your program is the only thing that is displayed (i.e. you won't see the REPL, as per your requirements).
Caveat: This will not work if your program prompts the user for input using the input procedures (e.g. read, read-char, read-line, etc.). This is because of the shell input redirection (<) (See: relevant question). Unfortunately, there is currently no proper way of executing an MIT Scheme script from the command line when input procedures are used. The best option is probably mit-scheme --quiet --load 'myscript', but you'd have to manually exit MIT Scheme when the script finishes. Relevant mailing list thread: [MIT-Scheme-devel] How to run a script and exit?
EDIT: Due to the possibility that you may mistype < as >, resulting in the overwrite of your source code, I would suggest encapsulating the above command within a shell script or a shell function. For example:
runscheme () {
scheme --quiet < "$1"
}
Then you can run runscheme program.scm without fear that your source code will be overwritten. (Special thanks to Paul Rooney for bringing this potential mistake to my attention).
References
scheme --help:
--batch-mode, --quiet, --silent
Suppresses the startup report of versions and copyrights, and the
valediction.
This command line option seems to have been mistakenly ommitted from the list of command line options in the documentation, but I think this is a legimate command line option because scheme --help shows it, and because --batch-mode is used in other parts of the reference manual (e.g. here).
I think what you want is SCM. You can execute a .scm script like this:
$ scm -f foo.scm arg1 arg2 arg3
See http://people.csail.mit.edu/jaffer/scm_3.html#SEC28 for more details.
The SCM homepage: http://people.csail.mit.edu/jaffer/SCM
checked chez --help, and then I found this(let's say that I'm using chez scheme):
chez --script ./temp.scm
Also, --verbose is very useful:
chez --verbose --script ./temp.scm