Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Branch: equal
Fetching contributors…

Cannot retrieve contributors at this time

122 lines (105 sloc) 4.273 kB
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.
^{:author "Chris Houser, Stuart Halloway",
:doc "Conveniently launch a sub-process providing its stdin and
collecting its stdout"}
(:use [ :only (as-file)])
(:import ( InputStreamReader OutputStreamWriter)))
(def *sh-dir* nil)
(def *sh-env* nil)
(defmacro with-sh-dir
"Sets the directory for use with sh, see sh for details."
{:added "1.2"}
[dir & forms]
`(binding [*sh-dir* ~dir]
(defmacro with-sh-env
"Sets the environment for use with sh, see sh for details."
{:added "1.2"}
[env & forms]
`(binding [*sh-env* ~env]
(defn- stream-seq
"Takes an InputStream and returns a lazy seq of integers from the stream."
(take-while #(>= % 0) (repeatedly #(.read stream))))
(defn- aconcat
"Concatenates arrays of given type."
[type & xs]
(let [target (make-array type (apply + (map count xs)))]
(loop [i 0 idx 0]
(when-let [a (nth xs i nil)]
(System/arraycopy a 0 target idx (count a))
(recur (inc i) (+ idx (count a)))))
(defn- parse-args
(let [default-opts {:out "UTF-8" :dir *sh-dir* :env *sh-env*}
[cmd opts] (split-with string? args)]
[cmd (merge default-opts (apply hash-map opts))]))
(defn- as-env-string
"Helper so that callers can pass a Clojure map for the :env to sh."
(nil? arg) nil
(map? arg) (into-array String (map (fn [[k v]] (str (name k) "=" v)) arg))
true arg))
(defn sh
"Passes the given strings to Runtime.exec() to launch a sub-process.
Options are
:in may be given followed by a String specifying text to be fed to the
sub-process's stdin.
:out option may be given followed by :bytes or a String. If a String
is given, it will be used as a character encoding name (for
example \"UTF-8\" or \"ISO-8859-1\") to convert the
sub-process's stdout to a String which is returned.
If :bytes is given, the sub-process's stdout will be stored in
a byte array and returned. Defaults to UTF-8.
:env override the process env with a map (or the underlying Java
String[] if you are a masochist).
:dir override the process dir with a String or
You can bind :env or :dir for multiple operations using with-sh-env
and with-sh-dir.
sh returns a map of
:exit => sub-process's exit code
:out => sub-process's stdout (as byte[] or String)
:err => sub-process's stderr (as byte[] or String)"
{:added "1.2"}
[& args]
(let [[cmd opts] (parse-args args)
proc (.exec (Runtime/getRuntime)
(into-array cmd)
(as-env-string (:env opts))
(as-file (:dir opts)))]
(println opts)
(if (:in opts)
(with-open [osw (OutputStreamWriter. (.getOutputStream proc))]
(.write osw (:in opts)))
(.close (.getOutputStream proc)))
(with-open [stdout (.getInputStream proc)
stderr (.getErrorStream proc)]
(let [[out err]
(if (= (:out opts) :bytes)
(for [strm [stdout stderr]]
(into-array Byte/TYPE (map byte (stream-seq strm))))
(for [strm [stdout stderr]]
(apply str (map char (stream-seq
(InputStreamReader. strm (:out opts)))))))
exit-code (.waitFor proc)]
{:exit exit-code :out out :err err}))))
(println (sh "ls" "-l"))
(println (sh "ls" "-l" "/no-such-thing"))
(println (sh "sed" "s/[aeiou]/oo/g" :in "hello there\n"))
(println (sh "cat" :in "x\u25bax\n"))
(println (sh "echo" "x\u25bax"))
(println (sh "echo" "x\u25bax" :out "ISO-8859-1")) ; reads 4 single-byte chars
(println (sh "cat" "myimage.png" :out :bytes)) ; reads binary file into bytes[]
Jump to Line
Something went wrong with that request. Please try again.