"inferior" process in clojure - redirecting asyncronous output for further processing "inferior" process in clojure - redirecting asyncronous output for further processing shell shell

"inferior" process in clojure - redirecting asyncronous output for further processing


To my knowledge, there's no Clojure specific machinery for managing processes. Also, OS processes fall out of the strictly-speaking Clojure focus, as it's a hosted language and process management is definitely something the host should manage (e.g. the JVM or CLR).

So, let's say you're running Clojure on the JVM. One facility that the JVM exposes to create subprocesses is the 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}))


You can take the InputStream returned by sh/proc, open it with reader, then use line-seq to create a lazy sequence of output lines. This should be run on another thread as doseq will block until output is available.

(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))))


This is enough to get started with.

(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"

Keeping in mind that "The agent system supports sharing changing state between threads in an asynchronous and independent manner" (clojure.org docs), I think that a sensible way to encapsulate this is:

(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")))

Then you can write e.g. @(feed-python "10+20") to send and receive results from python. This command will show the history of previous interactions as well as the latest one. For most use cases only the latest addition is relevant.

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