running shell commands with gnu clisp - lisp

I'm trying to create a "system" command for clisp that works like this
(setq result (system "pwd"))
;;now result is equal to /my/path/here
I have something like this:
(defun system (cmd)
(ext:run-program :output :stream))
But, I am not sure how to transform a stream into a string. I've reviewed the hyperspec and google more than a few times.
edit: working with Ranier's command and using with-output-to-stream,
(defun system (cmd)
(with-output-to-string (stream)
(ext:run-program cmd :output stream)))
And then trying to run grep, which is in my path...
[11]> (system "grep")
*** - STRING: argument #<OUTPUT STRING-OUTPUT-STREAM> should be a string, a
symbol or a character
The following restarts are available:
USE-VALUE :R1 Input a value to be used instead.
ABORT :R2 Abort main loop
Break 1 [12]> :r2

Something like this?
Version 2:
(defun copy-stream (in out)
(loop for line = (read-line in nil nil)
while line
do (write-line line out)))
(defun system (cmd)
(with-open-stream (s1 (ext:run-program cmd :output :stream))
(with-output-to-string (out)
(copy-stream s1 out))))
[6]> (system "ls")
"#.emacs#
Applications
..."

Per the CLISP documentation on run-program, the :output argument should be one of
:terminal - writes to the terminal
:stream - creates and returns an input stream from which you can read
a pathname designator - writes to the designated file
nil - ignores the output
If you're looking to collect the output into a string, you'll have to use a read-write copying loop to transfer the data from the returned stream to a string. You already have with-output-to-string in play, per Rainer's suggestion, but instead of providing that output stream to run-program, you'll need to write to it yourself, copying the data from the input stream returned by run-program.

You are asking specifically about clisp. I'll add here that
if you are using Clozure CL then you can also easily run os subprocesses.
Some examples:
;;; Capture the output of the "uname" program in a lisp string-stream
;;; and return the generated string (which will contain a trailing
;;; newline.)
? (with-output-to-string (stream)
(run-program "uname" '("-r") :output stream))
;;; Write a string to *STANDARD-OUTPUT*, the hard way.
? (run-program "cat" () :input (make-string-input-stream "hello") :output t)
;;; Find out that "ls" doesn't expand wildcards.
? (run-program "ls" '("*.lisp") :output t)
;;; Let the shell expand wildcards.
? (run-program "sh" '("-c" "ls *.lisp") :output t)
Do a search for run-program in the CCL docs located here: http://ccl.clozure.com/ccl-documentation.html
There are a couple nice Lisp ways of doing this in this stackoverflow answer: Making a system call that returns the stdout output as a string Once again, Rainer to the rescue. Thanks Ranier.

This is a shorter one
(defun system(cmd)
(ext:shell (string cmd)))
> (system '"cd ..; ls -lrt; pwd")

Related

cmucl: run-program output process exited only

I'm on debian stretch and install CMUCL from the stable repository. When trying the following
(run-program "sh" '("ls") :output T)
I get
#<process 2845 :EXITED>
without the expected output. Any help please?
Read ls(1) and dash(1) & bash(1). Notice that POSIX /bin/sh is often a symlink to one of them (often dash, sometimes bash). Be aware of the role of the PATH variable (e.g. in shells and for execvp(3)). And system(3) is using /bin/sh with -c, and your sh (assuming it is a POSIX shell) needs -c to interpret a string as a command.
You could try
(run-program "/bin/ls" nil :output t)
or
(run-program "/bin/sh" '("-c" "ls") :output t)
You probably don't have ls in your working directory (but somewhere else in your PATH; on POSIX it should be /bin/ls) and /bin/ls is not a shell script, but some ELF executable (on Linux; on most Unixes it is some executable and is not a script). So /bin/sh ls (or sh ls) can't work as you want it to.
To run ls there is no need to start a shell! And you might not need to fork a ls process for that purpose (of listing files), because you could call opendir(3) readdir(3) stat(2) closedir(3) as FFI (or find some package doing that).
PS. I have used SBCL to test these.
In CMUCL
(run-program "ls" '("-l") :input t :output t)
will work. This works fine, too:
(run-program "ls" '("-l" "-h") :input t :output t)
and if you need to produce a simple-string, to add it as an argument:
(run-program "ls"
`("-l" ,(format nil "~A" path-name))
:input t
:output t)
in that case you must use backquote (backtick) and comma.
It is also possible to do:
(defun my-ls (file)
(run-program "ls"
`("-l" "-h" ,file)
:input t
:output t))
now you have:
(my-ls "/home/user/")

executing N times in bach an emacs macro

I have an Emacs macro(named/saved) in a mymacro.el file
I would like to execute N times the macro in batch mode
like this:
emacs --batch -l ~/mymacro.el test.txt -f MyFoo
Question how to add N times in the lisp mymacro.el code ?
:-)
ErgoEmacs has a good page on this. What you want to do is reference the argv variable after calling emacs with the --script option. (elt argv 0) will give you the value of the first argument you specify. (elt argv 1) will give you the second argument, etc.
For example, let's say you call the following command:
emacs --script ~/mymacro.el ~/test.txt 22
Emacs will call the script ~/mymacro.el.
(elt argv 0) will give you "~/test.txt".
(elt argv 1) will give you "22".
(elt argv 3) will give you nil.
You can also use the variable command-line-args, outlined here.
To specifically batch-execute a macro a number of times, borrowing from this page on the wiki, we can create a script to run mymacro on a file, in the file run-mymacro.el:
;; Insert the macro here with `insert-kbd-macro`
(fset 'mymacro [...])
(defun : ()
(let ((file-name (elt argv 0))
(repeats (string-to-int (elt argv 1))))
(find-file file-name)
(condition-case nil
(execute-kbd-macro (symbol-function 'mymacro) repeats)
(error nil) )
(save-buffer) )
We can then call this script as so:
emacs --script ~/run-mymacro.el ~/test.txt 22
This will open the file ~/text.txt, run your macro 22 times and save the result. The original file should be saved in a backup with ~ at the end of the filename.
This is all untested, so it may need some tweaking before it works 100%.
As far as I understand your question, is it the case that you want to run the command emacs --batch -l ~/mymacro.el test.txt -f MyFoo N times?
If so, and if you are using Linux, it is easier to just use the for-loop of Linux shell.
#!/bin/bash
for i in {1..N} # replace N by your wanted number
do
emacs --batch -l ~/mymacro.el test.txt -f MyFoo
done

Launching shell command in Eshell without prompt interfence

I'm trying to customize my eshell to intercept python to do two things:
Open a new window and run python if no arguments are given (e.g., $ python)
Run the command "per usual" if arguments are given (e.g., $ python foobar.py)
So far I have something like this
(defun eshell/python (&rest cmd-args)
(if (not cmd-args)
(progn (run-python "python")
(select-window (split-window-below))
(switch-to-buffer "*Python*")
(balance-windows)
nil)
(message "Use '*python' to call python directly")))
I've tried replacing (message ...) with a few different things:
Based on the ouput of eshell-parse-command "python test.py" I tried
(progn (eshell-trap-errors
(eshell-named-command "python" cmd-args)))
but it hits a recursion limit.
Since *python test.py does what I want, I then tried
(progn (eshell-trap-errors
(eshell-named-command (concat "*" "python") cmd-args)))
but that puts the python process in the background and interrupts stdout with the output of my eshell-prompt-function.
Finally, I've fiddled with shell-command but I can't get it to write to the eshell buffer. In particular,
(progn (eshell-trap-errors
(shell-command (mapconcat (lambda(x) x) cmd-args " ")
(get-buffer "*eshell*") (get-buffer "*eshell*"))))
gives me a Text is read only message and moves point to start of the eshell buffer.
Is what I'm looking for possible?
Edit 1
Running without eshell/python defined, I've instead tried to avoid alias problems:
(defun eshell/gvr (&rest cmd-args)
(if (not cmd-args)
(progn (run-python "python")
(select-window (split-window-below))
(switch-to-buffer "*Python*")
(balance-windows)
nil)
(progn (eshell-trap-errors
(eshell-named-command "python" cmd-args)))))
If test.py is
print "Hello World"
x = raw_input("What should I repeat? ")
print x
running gvr test.py in eshell fails when I reply to the prompt because eshell tries to execute the input instead of handing it to python, but running python test.py goes off without a hitch.
How can I get run my own subprocesses in eshell the same way that they happen by default?

How to define an alternate command for emacs eshell

I'm trying to start using eshell in place of bash within emacs, but I rely heavily on bash functions that I have written over the years. I'd like to configure eshell to invoke bash whenever a "command not found" condition occurs, in case the command in question is implemented as a bash function.
There is a variable tantalizingly named eshell-alternate-command-hook that sounds like it is made to order, but my lack of elisp skill is interfering with my success I think.
This is my best effort:
(add-hook 'eshell-alternate-command-hook 'invoke-bash t t)
(defun invoke-bash (command args)
(throw 'eshell-replace-command
(list "bash -c" command args)))
But when I test it, it doesn't work:
c:/temp $ lsd
Wrong number of arguments: (lambda (command args) (throw (quote eshell-replace-command) (list "bash -c" command args))), 1
c:/temp $
This is what I eventually came up with:
(defun invoke-bash (command)
(progn
(setq invoke-bash-cmd (concat "bash -c \"" command " " (mapconcat 'identity eshell-last-arguments " ") "\""))
(message invoke-bash-cmd)
(throw 'eshell-replace-command
(eshell-parse-command invoke-bash-cmd))))
I'm not eshell guru, but in the place where this hook is used, I see that it receives only one argument - command, that you trying to execute, so your code could look like
(add-hook 'eshell-alternate-command-hook 'invoke-bash)
(defun invoke-bash (command)
(throw 'eshell-replace-command
(list "bash -c" command)))
but it doesn't work, because you need to return elisp function, not name of command (according to documentation). If you want to run bash, then you need to return string with full path to it, but I hadn't found how to pass additional arguments to bash. Maybe you can find more in corresponding section on Emacs Wiki?

How to process input and output streams in Steel Bank Common Lisp?

I'm trying to figure out how to use the output stream of one program I start with RUN-PROGRAM so it can be used as the input of another program started with RUN-PROGRAM (i.e., the moral and perhaps literal equivalent of piping). I've tried using a number of combinations of the :INPUT, :OUTPUT and :WAIT keyword arguments, but nothing I've hit
upon has been productive so far. Any tips would be helpful; for example, how would I go about doing something like ls | grep lisp from the shell?
One of my attempts is
(defun piping-test ()
(let ((grep-process (run-program "/usr/bin/grep" '("lisp")
:input :stream
:output :stream)))
(unwind-protect
(with-open-stream (s (process-input grep-process))
(let ((ls-process (run-program "/bin/ls" '()
:output s)))
(when ls-process
(unwind-protect
(with-open-stream (o (process-output grep-process))
(loop
:for line := (read-line o nil nil)
:while line
:collect line))
(process-close ls-process)))))
(when grep-process (process-close grep-process)))))
Running this in a SLIME REPL causes everything to hang until I break
with C-c C-c, so it's pretty obviously not the right thing, but I'm
not sure how to change it so it is the right thing.
EDIT: Adding :WAIT NIL to both RUN-PROGRAM invocations, or to only the invocation for grep, doesn't do the trick. In that case, the function will hang, and breaking with C-c C-c gets a stack trace indicating that there's a local function (defined via FLET) called SB-UNIX:SELECT that has hung.
I got a working answer from Raymond Toy on comp.lang.lisp. His solution was for CMUCL, but it worked with the essentially identical RUN-PROGRAM function on the closely related SBCL, and with minor changes it will work on CCL as well, because CCL's RUN-PROGRAM is basically a clone of the one from CMUCL/SBCL.
The secret, as it were, is to set up the ls process first, and then provide its output stream to the grep process as input, like so:
(defun piping-test2 ()
(let ((ls-process (run-program "/bin/ls" '()
:wait nil
:output :stream)))
(unwind-protect
(with-open-stream (s (process-output ls-process))
(let ((grep-process (run-program "/usr/bin/grep" '("lisp")
:input s
:output :stream)))
(when grep-process
(unwind-protect
(with-open-stream (o (process-output grep-process))
(loop
:for line := (read-line o nil nil)
:while line
:collect line))
(process-close grep-process)))))
(when ls-process (process-close ls-process)))))
I also experimented with omitting the :WAIT NIL argument from the RUN-PROGRAM call for ls, and it worked just as well.
Try adding :wait nil to your arguments to run-program. That should have both your grep and your ls running in the background. As is, you're starting the grep process, waiting for that to finish, then starting the ls you're intending to feed into the grep process. Alas, since you're waiting for the grep to finish, you never get that far.
Relatedly, but perhaps not spot on to your question, you could do:
(with-output-to-string (s)
(ccl:run-program "sh" (list "-c" "ls a/directory/somewhere/*.lisp") :output s)
s)
or
(with-output-to-string (s)
(ccl:run-program "sh" (list "-c" "ls /a/directory/somewhere/*.lisp | wc -l") :output s)
s)
or
(with-output-to-string (s)
(ccl:run-program "ssh" (list "a-user#some-ip" "sh -c ls /a/directory/somewhere/on/remote/server/*.lisp | wc -l") :output s)
s)
And, of course, you can use
(format nil "ls ~a" directory)
To get input, you can do something like:
(with-output-to-string (out)
(format t "~%Enter your sudo password:~%")
(with-input-from-string (s (read))
(ccl:run-program "ssh" (list *remote* "sudo cat /etc/init.d/nginx") :input s :output out))
out)