"inferior" clojure 中的进程 - 重定向异步输出以进行进一步处理
"inferior" process in clojure - redirecting asyncronous output for further processing
我想在 Clojure 中维护一个长期 运行 的交互式 shell 进程——在 Emacs 中称为 "inferior" 进程。我认为基本思想非常熟悉,因为 Clojure 本身可以通过 CIDER 运行 在 Emacs 内部。我很高兴知道是否有使用 Clojure 作为顶级流程的此设置的任何工作示例。
编辑:我发现了这个“shell" Gist, which is a very nice looking wrapper for 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中不打印有点奇怪(子问题...为什么2 不是打印出来的吗?)。无论如何,对于我的用例,我不需要它 printed;相反,我只想将它作为字符串取回。
第一次尝试:
(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 子进程的输出以供将来处理?
据我所知,没有专门用于管理流程的 Clojure 机制。此外,OS 进程不在严格意义上的 Clojure 重点范围内,因为它是一种托管语言,进程管理绝对是宿主应该管理的东西(例如 JVM 或 CLR)。
那么,假设您是 运行 JVM 上的 Clojure。 JVM 公开用于创建子进程的一种工具是 ProcessBuilder, which you could invoke from your Clojure code just like the following (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}))
您可以使用 sh/proc 返回的 InputStream,用 reader 打开它,然后使用 line-seq 创建一个延迟输出行序列。这应该是另一个线程上的 运行,因为 doseq 将阻塞直到输出可用。
(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))))
这足以开始。
(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"
记住 "The agent system supports sharing changing state between threads in an asynchronous and independent manner" (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)))
我想在 Clojure 中维护一个长期 运行 的交互式 shell 进程——在 Emacs 中称为 "inferior" 进程。我认为基本思想非常熟悉,因为 Clojure 本身可以通过 CIDER 运行 在 Emacs 内部。我很高兴知道是否有使用 Clojure 作为顶级流程的此设置的任何工作示例。
编辑:我发现了这个“shell" Gist, which is a very nice looking wrapper for 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中不打印有点奇怪(子问题...为什么2 不是打印出来的吗?)。无论如何,对于我的用例,我不需要它 printed;相反,我只想将它作为字符串取回。
第一次尝试:
(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 子进程的输出以供将来处理?
据我所知,没有专门用于管理流程的 Clojure 机制。此外,OS 进程不在严格意义上的 Clojure 重点范围内,因为它是一种托管语言,进程管理绝对是宿主应该管理的东西(例如 JVM 或 CLR)。
那么,假设您是 运行 JVM 上的 Clojure。 JVM 公开用于创建子进程的一种工具是 ProcessBuilder, which you could invoke from your Clojure code just like the following (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}))
您可以使用 sh/proc 返回的 InputStream,用 reader 打开它,然后使用 line-seq 创建一个延迟输出行序列。这应该是另一个线程上的 运行,因为 doseq 将阻塞直到输出可用。
(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))))
这足以开始。
(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"
记住 "The agent system supports sharing changing state between threads in an asynchronous and independent manner" (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)))