"劣" clojure中的进程 - 重定向异步输出以进行进一步处理

时间:2016-08-23 14:36:05

标签: shell clojure future

我想在Clojure中维护一个长期运行的交互式shell进程 - 这将被称为"劣等" Emacs中的过程。我认为基本的想法非常熟悉,因为Clojure本身可以通过CIDER在Emacs内部运行。我很高兴知道使用Clojure作为顶级流程是否有任何有关此设置的实例。

编辑:我发现了这个" shell" Gist,这是一个非常好看的Conch封面,给了我一些思考的东西。

使用我自己的first attempt with Conch:我无法将字符串输入python,并以与我相同的方式获得输出,例如cat。但通过一些实验,我找出了基础知识并绕过了第一个障碍。

(require '[me.raynes.conch.low-level :as sh])
(def sh-python (sh/proc "python" "-i"))      ; "-i" needed to get interactive mode
#'flowrweb.core/sh-python
(future (sh/stream-to-out sh-python :out))   
#future[{:status :pending, :val nil} 0x516f3fff]
(sh/feed-from-string sh-python "1+1\n")      ; just returns nil on the CIDER repl
2                                            ; <- but we see "2" with the lein repl 
nil

所以我知道我感兴趣的数据是可用的,虽然它有点奇怪,它没有在CIDER中打印(子问题 ...为什么不是&n #2; 2打印?)。无论如何,对于我的用例,我不需要它打印;相反,我只是想把它作为一个字符串。

第一次尝试:

(def pyout (future (sh/stream-to-string sh-python :out)))
;=> #'flowrweb.core/py-out
(sh/feed-from-string sh-python "1+2\n")
;=> 3
;=> nil
@pyout
^C                     ; <- the process hangs

似乎sh/stream-to-string并没有做我需要的事情。

如何使用with-out-str

(def something (with-out-str (sh/feed-from-string sh-python "1+3\n")))
;=> 4
;=> #'user/something
something
;=> ""                 ;<- where is the "4"?

不,那也不起作用。

tl; dr :如何重定向Conch子流程的输出以供将来处理?

3 个答案:

答案 0 :(得分:0)

据我所知,没有用于管理流程的Clojure特定机器。此外,操作系统进程不属于严格意义上的Clojure焦点,因为它是托管语言,而进程管理肯定是主机应该管理的东西(例如JVM或CLR)。

所以,让我们说你在JVM上运行Clojure。 JVM公开用于创建子进程的一个工具是ProcessBuilder,您可以从Clojure代码中调用,如下所示(credits go to @codification):

(ns proc
  (:import [java.lang ProcessBuilder])
  (:use [clojure.java.io :only [reader writer]]))

(defn spawn [& args]
  (let [process (-> (ProcessBuilder. args)
                  (.start))]
    {:out (-> process
            (.getInputStream)
            (reader))
     :err (-> process
            (.getErrorStream)
            (reader))
     :in (-> process
           (.getOutputStream)
           (writer))
     :process process}))

答案 1 :(得分:0)

您可以使用sh / proc返回的InputStream,使用reader打开它,然后使用line-seq创建一个延迟的输出行序列。这应该在另一个线程上运行,因为剂量将阻塞,直到输出可用。

(let [{out-stream :out} (sh/proc "ls" "-l")]
  (with-open [out-rdr (clojure.java.io/reader out-stream)]
    (doseq [line (line-seq out-rdr)]
      ; do something with line: Like feed it into core.async chan
      ; (>!! some-chan line)
      ; or pass it to some fn
      (println line))))

答案 2 :(得分:0)

这足以开始使用。

(require '[me.raynes.conch.low-level :as sh])
;=> nil
(def my-stringwriter (java.io.StringWriter.))
;=> #'user/my-stringwriter
(def sh-python (sh/proc "python" "-i"))
;=> #'user/sh-python
(future (sh/stream-to sh-python :out my-stringwriter)) ; NOT redirecting *out* 
;=> #future[{:status :pending, :val nil} 0x4358e46d]
(sh/feed-from-string sh-python "1+1\n")
;=> nil
(.toString my-stringwriter)
;=> "2\n"
(sh/feed-from-string sh-python "1+2\n")
;=> nil
(.toString my-stringwriter)
;=> "2\n3\n"

请记住“代理系统支持以异步和独立的方式在线程之间共享更改状态”(clojure.org docs),我认为封装它的一种合理方法是:

(require '[clojure.string :as str])

(def a-stringwriter (agent (java.io.StringWriter.)))
(future (sh/stream-to sh-python :out @a-stringwriter))

(defn feed-python [user-input]
  (future (sh/feed-from-string sh-python (str user-input "\n"))
          (Thread/sleep 1000)
          (str/split (.toString @a-stringwriter) #"\n")))

然后你可以写例如@(feed-python "10+20")发送和接收python的结果。此命令将显示先前交互的历史记录以及最新的交互历史记录。对于大多数用例,只有最新的添加是相关的。

(defn gljcon [user-input]
  (last @(feed-python user-input)))