On Thursday 18 December 2008 12:07, Stephan Mühlstrasser wrote: > Hi, > > I've not yet seen any examples on how to deal with external processes > in Clojure (I hope I didn't overlook something in clojure-contrib). > > The following is my attempt to start a sub-process and to pass > through stdout and stderr. The shell command prints out 1000 lines > "hello" and a final "command finished". The problem is that nothing > is printed by the Clojure program. If I increase the number of lines > for example to 2000 (change "head -1000" to "head -2000"), I see a > lot of output, but it is cut off somewhere in the middle and the > final "command finished" does never appear.
I just did something virtually identical to this: user=> (sh date) Thu Dec 18 12:19:42 PST 2008 (That's a macro for the convenience of not having to either use string literals or quote the arguments. The real work is done in a function.) > ... > > (let [pb (new ProcessBuilder ["sh" "-c" "yes hello | head -1000; echo > command finished"]) > proc (.start pb) > stdout (reader (.getInputStream proc)) > stderr (reader (.getErrorStream proc)) > stdout-agent (agent stdout) > stderr-agent (agent stderr)] > (send stdout-agent copy (writer *out*)) > (send stderr-agent copy (writer *err*)) > (await stdout-agent stderr-agent) > (.waitFor proc) > (shutdown-agents) > (println "done")) > > Is this use of agents incorrect? I would say it's an appropriate use, but you need to do it a little differently: First of all, use (send-off ...) or you'll have to wait for the agent to complete. Then use (await ...) on the agents. > Why can the program terminate before all the output from the sub- > process has been passed through? As long as the sub-process produces no more output than the operating system's pipe buffering limit, it can complete without blocking. > Is there a better way to synchronize with sub-processes in Clojure, > or is it necessary to synchronize completely at the Java level? I don't understand this question. Here's what my implementation looks like. It does not stand alone as shown, but you can probably figure out what the missing pieces do: (def *shell* "bash") (def *shopt* "-c") (defn- cat-proc-stream "Copy all the bytes from stream to the writer" [stream writer] (binding [*out* writer] (cat-stream stream))) (defn shf "Invoke a platform / external command" [& args] (let [out *out* err *err* cmd+args (flatten args) builder (if (and (= (count cmd+args) 1) (string? (first cmd+args))) (ProcessBuilder. (into-array (conj [] *shell* *shopt* (first cmd+args)))) (ProcessBuilder. (into-array (map str cmd+args)))) process (.start builder) stdout-copier (agent nil) stderr-copier (agent nil)] (send-off stdout-copier #(cat-proc-stream %2 err) (.getErrorStream process)) (send-off stderr-copier #(cat-proc-stream %2 out) (.getInputStream process)) (await stdout-copier stderr-copier)) ) (defmacro sh "Invoke a platform / external command without evaluating arguments" [& args] `(shf '~args)) > Thanks > Stephan Randall Schulz --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en -~----------~----~----~----~------~----~------~--~---