ClojureDocs

Nav

Namespaces

sh

clojure.java.shell

Available since 1.2
  • (sh & args)
Passes the given strings to Runtime.exec() to launch a sub-process.
 Options are
 :in      may be given followed by any legal input source for
         clojure.java.io/copy, e.g. InputStream, Reader, File, byte[],
         or String, to be fed to the sub-process's stdin.
:in-enc  option may be given followed by a String, used as a character
         encoding name (for example "UTF-8" or "ISO-8859-1") to
         convert the input string specified by the :in option to the
         sub-process's stdin.  Defaults to UTF-8.
         If the :in option provides a byte array, then the bytes are passed
         unencoded, and this option is ignored.
:out-enc 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 java.io.File.
 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 (String via platform default encoding)
12 Examples
user=> (use '[clojure.java.shell :only [sh]])

;; Note: The actual output you see from a command like this will look messier.
;; The output below has had all newline characters replaced with line
;; breaks.  You would see a big long string with \n characters in the middle.
user=> (sh "ls" "-aul")

{:exit 0, 
 :out "total 64
drwxr-xr-x  11 zkim  staff    374 Jul  5 13:21 .
drwxr-xr-x  25 zkim  staff    850 Jul  5 13:02 ..
drwxr-xr-x  12 zkim  staff    408 Jul  5 13:02 .git
-rw-r--r--   1 zkim  staff     13 Jul  5 13:02 .gitignore
-rw-r--r--   1 zkim  staff  12638 Jul  5 13:02 LICENSE.html
-rw-r--r--   1 zkim  staff   4092 Jul  5 13:02 README.md
drwxr-xr-x   2 zkim  staff     68 Jul  5 13:15 classes
drwxr-xr-x   5 zkim  staff    170 Jul  5 13:15 lib
-rw-r--r--@  1 zkim  staff   3396 Jul  5 13:03 pom.xml
-rw-r--r--@  1 zkim  staff    367 Jul  5 13:15 project.clj
drwxr-xr-x   4 zkim  staff    136 Jul  5 13:15 src
", :err ""}
user=> (use '[clojure.java.shell :only [sh]])

user=> (println (:out (sh "cowsay" "Printing a command-line output")))

 _________________________________ 
< Printing a command-line output. >
 --------------------------------- 
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

nil
user=> (use '[clojure.java.shell :only [sh]])
nil

;; note that the options, like :in, have to go at the end of arglist
;; advantage of piping-in thru stdin is less need for quoting/escaping
user=> (println (:out (sh "cat" "-" :in "Printing input from stdin with funny chars like ' \" $@ & ")))
Printing input from stdin with funny chars like ' " $@ & 
nil
;; sh is implemented using Clojure futures.  See examples for 'future'
;; for discussion of an undesirable 1-minute wait that can occur before
;; your standalone Clojure program exits if you do not use shutdown-agents.
(sh "pwd" :dir "/home/ics/icsdev")
{:exit 0, :out "/home/ics/icsdev\n", :err ""}
(require '[clojure.java.shell :as shell])
(shell/sh "sh" "-c" "cd /etc; pwd")
{:exit 0, :out "/etc\n", :err ""}
;; note that you have to split you script by whitespace 
;; that was confusing for me 
;; for example script: 
;; "terraform plan -var param1=value1 -var param2=value2 -var-file=/etc/var.tfvars"

(shell/sh "terraform" "plan" "-var" "param=value" "-var" "param2=value2" "-var-file=/etc/var.tfvars")
;; feed the sh with environment variables
;; (note that $HELLO in our command line wouldn't work since we act as the shell here)

(require '[clojure.java.shell :as shell])
(shell/sh "printenv" "HELLO" :env {"HELLO" "Hello, World!"})
{:exit 0, :out "Hello, World!\n", :err ""}

;; override environment variables
;; BAD EXAMPLE since i guess lein uses a lot of env vars for coordination
;; but anyway

(require '[clojure.java.shell :as shell])

(sh/sh "lein" "compile")
{:exit 0,
 :out
 "WARNING: You have $CLASSPATH set, probably by accident.
  It is strongly recommended to unset this before proceeding.",
 :err ""}

;; note that
(sh/sh "lein" "compile" :env {})
;; will make lein quite confused.

;; we better modify the current env:

(let [current-env (into {} (System/getenv))]
  (sh/sh "lein" "compile" :env (dissoc current-env "CLASSPATH"))
{:exit 0, :out "", :err ""}


;; (into {} ...) is needed because System/getenv returns a map of type
;; java.util.Collections$UnmodifiableMap
;; on windows, the above examples won't work. try with an exe instead :)
(require '[clojure.java.shell :as shell])
(shell/sh "notepad.exe")
;; now notepad should be open!
;; On Windows, the Java shell needs to execute a program 
;; that executes the command: usually PowerShell, Bash, or cmd. 

;; So your `sh` function should invoke "powershell" OR "bash" and -c" 
;; OR "cmd and "/c" before your command. 

;; Instead of 
(clojure.java.shell/sh "ls")

;; you would do this

(clojure.java.shell/sh "powershell" "ls")

;; or this

(clojure.java.shell/sh "bash" "-c" "ls")

;; or this

(clojure.java.shell/sh "cmd" "/c" "dir") ;; No `ls` or `pwd` in cmd. 

;; (I first learned about the need for command flags like "/c" here: 
;; https://stackoverflow.com/a/4031412/2596132

;; These Microsoft docs explain the "/c" flag: 
;; https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd

;; I learned of bash's "-c" flag after finding `exec` in `bash -c help` as suggested
;; from `bash --help. I didn't know that `-c` was the command flag at the time.)
;; To use pipes and whatnot, a simple way is to run eg `bash -c` as the command
;; and the rest as a single string
(clojure.java.shell/sh "/bin/bash" "-c" "ls -la | grep foo")
See Also

Sets the directory for use with sh, see sh for details.

Added by jafingerhut

Sets the environment for use with sh, see sh for details.

Added by jafingerhut

Takes a body of expressions and yields a future object that will invoke the body in another thread...

Added by jafingerhut

Initiates a shutdown of the thread pools that back the agent system. Running actions will complete...

Added by djblue
2 Notes
    By , created 7.9 years ago

    It's worth noting that sh begins interpreting arguments starting with the first non-string (not just keywords!) as key-value pairs, as in the example above with pwd. This means that even if an argument's type has a trivial conversion to a string, such as an integer or boolean, it must be stringified. If not, it'll be passed as an argument to hash-map, and you might see an IllegalArgumentException if there are an odd number of arguments beginning with the first non-string.

    By , created 6.2 years ago

    As noted in the 4th example, sh uses futures. This means that if your program uses sh and then finishes its execution it will unexpectedly hang and not terminate/exit. The sh future will still be alive in the background and will be holding up the program.

    This is a bit confusing when you first try to use Clojure for scripting as it looks like your script doesn't exit naturally. Furthermore, when you run sh in the REPL the background futures aren't apparent to the user and everything works as-expected

    To fix the situation you can either run (System/exit 0) to terminate your program explicitly. Or you can run (shutdown-agents) to kill the background future and then the program will exit naturally

    For a discussion of this strange behavior see: https://clojureverse.org/t/why-doesnt-my-program-exit/3754/2