Updated to reflect some feedback and one example of using commons-exec as an alternative to the plain old
Second Update to reflect use of
shell-out – thanks Scott!
Making use of
(ns utils (:use clojure.contrib.duck-streams)) (defn execute [command] (let [process (.exec (Runtime/getRuntime) command)] (if (= 0 (.waitFor process)) (read-lines (.getInputStream process)) (read-lines (.getErrorStream process))))) ... user=> (execute "ls") ("MyProject.iml" "lib" "out" "src" "test")
It could be improved obviously – for example catching some of the potential
IOExceptions that can result to rethrow additional information, such as the command being executed, or the ability to take a
seq of program arguments.
Error & Argument Handling
This version adds some basic (and ugly) exception handling, and also handles spacing out arguments passed in (so passing
"ls" "-la" gets processed into
(defn execute "Executes a command-line program, returning stdout if a zero return code, else the error out. Takes a list of strings which represent the command & arguments" [& args] (try (let [process (.exec (Runtime/getRuntime) (reduce str (interleave args (iterate str " "))))] (if (= 0 (.waitFor process)) (read-lines (.getInputStream process)) (read-lines (.getErrorStream process)))) (catch IOException ioe (throw (new RuntimeException (str "Cannot run" args) ioe)))))
I had some problems with hanging processes, so knocked up a version using Apache’s
commons-exec. This version has the added advantage of killing long-running processes, and I folded in Steve’s suggestion for a better way of splicing in the spaces in the command line args (see his comment).
commons-exec is part of the special sauce inside Ant, so is a rock solid way of launching command-line processes (well, as rock solid as Java gets).
The use of the
ByteArrayOutputStream is probably inefficient, and again, decent error handling is left as an exercise to the reader.
(defn alternative-execute "Executes a command-line program, returning stdout if a zero return code, else the error out. Takes a list of strings which represent the command & arguments" [& args] (let [output-stream (new ByteArrayOutputStream) error-stream (new ByteArrayOutputStream) stream-handler (new PumpStreamHandler output-stream error-stream) executor (doto (new DefaultExecutor) (.setExitValue 0) (.setStreamHandler stream-handler) (.setWatchdog (new ExecuteWatchdog 20000)))] (if (= 0 (.execute executor (CommandLine/parse (apply str (interpose " " args))))) (.toString output-stream) (.toString error-stream))))
Many thanks to Scott for this.
clojure.contrib supplies the very neat
user=> (use 'clojure.contrib.shell-out) nil user=> (sh "ls" "-la")
I haven’t probed further to see if this deals with my hanging process problem, but it certainly doesn’t seem to have any support for killing timeout processes. If you’re worried about runaway tasks, the
commons-exec version above might be the right choice for you.