Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait for / capture aysnchronous shell command output in emacs lisp?

Tags:

elisp

If I execute a shell command asynchronously in emacs lisp like so:

(shell-command "mycommand &")

Is there a way to wait for the command to generate output before continuing? For my current application, it is probably sufficient to wait until the command generates any output at all, but ideally I'd like to capture the output for additional processing. Is this possible?

like image 498
Mike K Avatar asked Feb 26 '23 09:02

Mike K


2 Answers

You should use comint-output-filter-functions variable that contains function to call after output is inserted into the buffer.

For example, you can do :

(add-hook 'comint-output-filter-functions '(lambda (txt) (message "hello")))

N.B. : From Emacs 23.2, you have the new command async-shell-command, bound globally to M-&. This executes your command asynchronously without requiring an ampersand. The output of your command is sent to the buffer *Async Shell Command*.

like image 151
Jérôme Radix Avatar answered Apr 29 '23 04:04

Jérôme Radix


Perhaps you need to register a Process Filter to give you the callback timing you need? See 37.9 Receiving Output from Processes in the Elisp manual (I see this in my copy for Emacs 22.3).

Here is an example of running a callback when you get the first process output and also storing it into an "associated buffer". Copy it to your *scratch* buffer and eval-region it, but make sure to split-window and show the *Messages* buffer visible so that you can see what's going on.

;; this is emacs lisp (and a comment line)
(defvar my-callback-got-some-already nil)

(defun my-callback ()
  (message "callback ran at   %s" (current-time-string)))

(defun my-filter-waits-for-first-time-input (proc string)
  (unless my-callback-got-some-already
    (setq my-callback-got-some-already t)
    ;; do your one-time thing
    (my-callback))
  ;; insert into the associated buffer as if no process filter was
  ;; registered
  (with-current-buffer (process-buffer proc)
    (let ((moving (= (point) (process-mark proc))))
      (save-excursion
        ;; Insert the text, advancing the process marker.
        (goto-char (process-mark proc))
        (insert string)
        (set-marker (process-mark proc) (point)))
      (if moving (goto-char (process-mark proc))))))

(defun async-callback-test-harness ()
  (interactive)
  (let ((process-handle "async-callback-test")
        (associated-process-buffer "*async-callback-test*")
        (command "ls")
        (busy-loop-var ""))
  (setq my-callback-got-some-already nil)
  (message "start test        %s" (current-time-string))
  (start-process process-handle associated-process-buffer command)
  ;; Supposedly async but Emacs doesn't get the input until
  ;; "Emacs is waiting" so the following set-process-filter
  ;; can be registered in time.
  ;; To prove the point, make emacs busy loop to show that the
  ;; emacs doesn't drop its input and 
  ;; the callback will get the unskipped input.
  (switch-to-buffer associated-process-buffer)
  (dotimes (k 2000) ; about two seconds on my machine
    (setq busy-loop-var (concat busy-loop-var "busy looping...")))
  (message "done busy waiting %s" (current-time-string))
  (set-process-filter (get-process process-handle)
                      'my-filter-waits-for-first-time-input)
  nil))

;; run it!
(async-callback-test-harness)
like image 24
piyo Avatar answered Apr 29 '23 05:04

piyo