如何在Emacs Lisp中等待/捕获异步Shell命令的输出?

4
如果我在Emacs Lisp中异步执行一个shell命令,像这样:
(shell-command "mycommand &")
有没有办法在继续之前等待命令生成输出?对于我的当前应用程序,等待命令生成任何输出可能足够了,但理想情况下,我想捕获输出以进行其他处理。这个可能吗?

我不明白:你想等待响应,因此这是一个同步过程。那么为什么你想用“&”异步地启动你的进程? - Jérôme Radix
该进程是一个服务器并且不会终止。我实际上正在等待服务器启动后再连接。 - Mike K
2个回答

4
你应该使用 comint-output-filter-functions 变量,该变量包含在输出插入到缓冲区后要调用的函数。
例如,你可以这样做:
(add-hook 'comint-output-filter-functions '(lambda (txt) (message "hello")))

注意:从Emacs 23.2开始,您可以使用全局绑定为M-&的新命令async-shell-command。这将异步执行您的命令而无需使用&符号。您的命令输出将发送到缓冲区*Async Shell Command*

我仍然在23.1版本上,所以我没有这个命令,但是我可以通过shell-command和&符号获得相同的效果。不过,我需要某种机制来等待(或回调)缓冲区中的输出出现。 - Mike K
1
起初我无法运行钩子函数。顺便提一下,我在Windows 7下运行23.1.1。然而,你提到的comint-output-filter-functions引导我找到了以下链接:http://www.squidoo.com/emacs-comint该示例将钩子与make-comint结合使用以生成进程,这对我很有效。 - Mike K
这很完美,我使用它与make-comint-in-buffer一起运行异步命令并在执行时获取命令的输出。 - jcubic

4
也许您需要注册一个进程过滤器来获得所需的回调时间?请参见37.9 Elisp手册中的进程输出接收(我在Emacs 22.3副本中看到了这个)。
以下是一个示例,当您获得第一个进程输出并将其存储到“相关缓冲区”中时运行回调。将其复制到您的*scratch*缓冲区并进行eval-region,但请确保分割窗口并显示*Messages*缓冲区可见,以便您可以看到发生了什么。
;; 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)

这看起来很有前途,但我从未能够调用回调函数。请参阅我在Jérôme Radix答案上的评论以获取更多详细信息。 - Mike K
Piyo,我喜欢你的解决方案,但是它没有正常工作。我有一个正在运行编译命令的shell,所以它会忙一段时间。现在,当执行命令(async-callback-test-harness)时,它会在某种程度上中断运行、ls目录,然后继续运行。因此,它并没有等待shell命令完成。是否有解决方法? - SFbay007

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接