在执行另一个elisp命令之前,请等待shell命令完成

4
我正在尝试做以下事情:
  1. 在emacs中打开shell并进入项目文件夹
  2. 从仓库执行mercurial pull
  3. “等待”直到pull完成
  4. 告知elisp命令,它启动了shell命令,所以它可以继续编译命令来构建树。
在.emacs文件内,是否有一种方法/代码可以让我知道shell命令何时完成,然后才开始新的elisp命令?

一旦Emacs启动了异步进程,就没有办法知道它何时完成。事情变得更加复杂,如果您正在打开交互式shell,则当hg pull完成时,该进程实际上并没有完成,它仍然在运行!在这种情况下,大多数项目将只使用两个命令-一个用于步骤1和2,另一个用于步骤4。ESS(http://ess.r-project.org/)在类似的情境中正是如此。 - Tyler
2
这是我使用的-- start-processset-process-sentinel: http://stackoverflow.com/a/18707182/2112489。这个例子是针对latexmk的,但是对于其他东西也应该可以正常工作。 - lawlist
1
谢谢 Lawlist,这个方法可行! - SFbay007
3个回答

3
我认为您不需要启动一个物理终端来实现这个目的。在您的elisp中,您可以使用例如call-process-shell-command,它会等待您输入的命令完成,比如"cd目录; hg pull"
为了进行测试,您可以在*scratch*窗口中尝试(C-x C-e):
(call-process-shell-command "cd directory ; ls" nil t)

当你输入该命令后,它会显示光标前目录下的文件列表,并等待执行下一个命令直到该命令完成。


感谢您的回复,迭戈。我想要在emacs中启动一个shell,这样我就可以看到拉取进度。有时会出现需要进一步操作的合并复杂情况。因此,shell命令将处理拉取。我需要一种等待它完成的方法,这样我就可以使用编译命令开始制作过程。我也可以从shell内部使用make自身,但我喜欢编译程序提供的功能,它显示编译期间的错误,并具有直接跳转到错误的选项。 - SFbay007
Diego,是否有一种方法可以将进程调用的输出主动转发到emacs缓冲区中? - SFbay007
是的,只需阅读start-process的文档。使用它启动一个进程,并将输出定向到特定的缓冲区,在它启动后,使用display-buffer显示输出。start-process将返回进程对象。使用set-process-sentinel设置进程的sentinel函数(请阅读sentinel的文档),使用sentinel在进程成功完成时执行elisp。 - Jordon Biondo
正如Tyler在上面的评论中所说,您可以使用任何start-processstart-process-shell-command的变体来启动进程并“断开”输入,或者使用任何shell缓冲区并且失去有关进程何时完成的信息(尽管您可以像在shell缓冲区中一样控制合并错误)。我想到的另一件事是,您可以启动Mercurial进程,以便在发现冲突时失败。这样,您就可以得知错误并自行处理冲突。由于冲突往往很少,因此这将在90%的时间内起作用,并且不需要任何操作。 - Diego Sevilla

2

@Francesco之前在以下链接中回答了一个类似的问题:https://stackoverflow.com/a/18707182/2112489

我发现将start-process作为一个let绑定变量使用会导致它立即运行,而不是等待到函数稍后更合适的时机。我还发现set-process-sentinel会阻止以前定义的let绑定变量的使用。与其使用let绑定变量或全局变量,另一个选项是使用缓冲区本地变量——例如,(defvar my-local-variable nil "This is my local variable.") (make-variable-buffer-local 'my-local-variable)——并在函数内设置它——例如,(setq my-local-variable "hello-world")。尽管我看到的大多数示例都将哨兵分解出来,但我喜欢将所有内容放入一个函数中——例如,下面的代码片段所示:

(set-process-sentinel 
  (start-process
    "my-process-name-one"
     "*OUTPUT-BUFFER*"
    "/path/to/executable"
    "argument-one"
    "argument-two"
    "argument-three")
  (lambda (p e) (when (= 0 (process-exit-status p))
    (set-process-sentinel 
      (start-process
        "my-process-name-two"
        nil ;; example of not using an output buffer
        "/path/to/executable"
        "argument-one"
        "argument-two"
        "argument-three")
      (lambda (p e) (when (= 0 (process-exit-status p))
        (set-process-sentinel 
          (start-process . . . ))))))))

lawlist,我无法弄清楚my-local-variable的目的。您能否在上面的示例中包含它以了解其效果? - SFbay007
当存在太多具有相同名称和值的全局变量时,某些函数可能会失败,如果它们恰好使用相同的变量--例如filenamefile-name。通过将变量定义为缓冲区本地变量,在除了正在使用它的那个缓冲区之外的所有地方都是nil,并且一旦关闭缓冲区,它就会消失。我更喜欢使用let绑定变量,但在set-process-sentinel内部效果不佳。因此,最好的选择似乎是使用缓冲区本地变量来缩短脚本编写时间,并避免重复相同的长值。 - lawlist
1
这里有一个链接,指向一个更新的示例,该示例使用缓冲区本地变量来代替全局或let绑定变量,以缩短脚本编写时间:http://stackoverflow.com/q/18705774/2112489。另外还有一个示例:https://dev59.com/0H7aa4cB1Zd3GeqPtKqX。 - lawlist
1
只是为了澄清,无论您是否仅将缓冲区本地值分配给变量,仍需要适当命名变量(以避免与其他变量发生潜在的名称冲突)。 - phils
lexical-let 是另一个选项。 - unhammer
请注意,如果您想在上一个进程完成后启动下一个进程,则使用process-exit-status进行测试是不合适的。例如,如果p被停止或唤醒,(process-exit-status p)也会返回0。该进程可能会从Emacs外部接收到此类信号。如我在这里提到的那样,请改用process-status - Tobias

0
(defun async-proc-with-context ()
  "experiment! set process completion context using the plist"
  (let* ((cmd "ls -la")
         (ctx "it finished")
         (output-buffer (generate-new-buffer (format "*bufname*")))
         (proc (progn
                 (async-shell-command cmd output-buffer)
                 (get-buffer-process output-buffer))))
    (if (process-live-p proc)
        (progn
          (process-put proc 'jw-ctx ctx) ;; set the context
          (set-process-sentinel proc '(lambda (process signal)
                                        (when (memq (process-status process) '(exit signal))
                                          (message (process-get process 'jw-ctx)) ;; get the context
                                          (shell-command-sentinel process signal))))))))

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