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
-~----------~----~----~----~------~----~------~--~---

Reply via email to