Elisp 解析异步 shell 命令的输出结果

3

我有一个简单的elisp交互函数,用于启动Clojure repl。

(defun boot-repl ()
  (interactive)
  (shell-command "boot repl wait &"))

它打开了一个*Async Shell Command*缓冲区,一段时间后会出现以下文本: nREPL服务器已在主机127.0.0.1的端口59795上启动-nrepl:// 127.0.0.1:59795 隐式目标目录已弃用,请改用目标任务。将BOOT_EMIT_TARGET = no设置为禁用隐式目标dir。 我想监视此命令的输出以便解析端口(在此示例中为“59795”)。即使只有第一行(在没有警告的情况下)也可以。 这样,我就能够使用另一个命令连接到等待我的Clojure REPL。 我无法使用shell-command-to-string,因为该命令不返回并且永久阻止emacs(boot repl wait应持续整个编程会话,可能更长时间)。 使用cider也可能有简单的解决方法,但我还没有找到。

那么,我如何在Elisp中解析异步bash命令的结果? 或者,我如何设置Cider来启动这个REPL并连接到它?

3个回答

2
直接回答这个问题,你肯定可以使用 start-processset-process-filter 解析异步 shell 命令的输出:
(let ((proc (start-process "find" "find" "find" 
                  (expand-file-name "~") "-name" "*el")))
     (set-process-filter proc (lambda (proc line)
                    (message "process output: %s" line))))

(过滤函数文档)

但是,请注意上面的line不一定是一行,可能包括多行或者断开的行。当进程或emacs决定刷新一些输出时,会调用你的过滤器:

... /home/user/gopath/src/github.com/gongo/json-reformat/test/json-reformat-test.el /home/user/gopath/src/github.com/gongo/json-reformat/test/test- 进程输出:helper.el

在你的情况下,这可能意味着你的端口号可能被分成两个单独的process-filter调用。

为了解决这个问题,我们可以引入一个行缓冲和行拆分的包装器,为每个进程输出行调用你的过滤器:

(defun process-filter-line-buffer (real-filter)
 (let ((cum-string-sym (gensym "proc-filter-buff"))
       (newline (string-to-char "\n"))
       (string-indexof (lambda (string char start)
             (loop for i from start below (length string)
                   thereis (and (eq char (aref string i))
                        i)))))

   (set cum-string-sym "")
   `(lambda (proc string)
      (setf string (concat ,cum-string-sym string))
      (let ((start 0) new-start)
    (while (setf new-start
            (funcall ,string-indexof string ,newline start))

      ;;does not include newline
      (funcall ,real-filter proc (substring string start new-start))

      (setf start (1+ new-start)));;past newline

    (setf ,cum-string-sym (substring string start))))))

然后,您可以放心地期望您的代码行是完整的:
(let* ((test-output "\nREPL server started on port 59795 on host 127.0.0.1 - \nrepl://127.0.0.1:59795 Implicit target dir is deprecated, please use the target task instead. Set BOOT_EMIT_TARGET=no to disable implicit target dir.")
     (proc (start-process "echo-test" "echo-test" "echo" test-output)))
 (set-process-filter proc (process-filter-line-buffer
               (lambda (proc line)
                 (when (string-match
                    "REPL server started on port \\([0-9]+\\)"
                    line)
                   (let ((port (match-string 1 line)))
                 ;;take whatever action here with PORT
                 (message "port found: %s" port)))))))

最后,我对苹果酒并不熟悉,但这种低级别的工作可能属于一种较低的模式,并且可能已经被解决了。

1
shell-command

允许命名可选的输出和错误缓冲区。然后,错误应该出现在后者中,而不会再混淆输出。

1

对于我之前提供的另一个答案,更好的方法是像你建议的那样直接使用cider:

(progn
     (package-refresh-contents)
     (package-install 'cider)
     (cider-jack-in))

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