Pallet scripts are written using an embedding of shell script in Clojure, and allows for abstraction of operating system variations.

At it's simplest, the pallet.stevedore/script macro takes one or more forms and converts them to shell script.

(require '[pallet.stevedore :refer [script with-script-language]])
(require 'pallet.stevedore.bash) ;; for bash output

(with-script-language :pallet.stevedore.bash/bash
  (script
    ("ls")))

results in:

ls

The function position is resolved (see Script functions below), so needs to be a string if you want to pass a literal.

For the ease of trying things at the REPL, the following examples allow you to drop the with-script-language wrapping form. N.B. do not do this in your code!

(.bindRoot #'pallet.stevedore/*script-language* :pallet.stevedore.bash/bash)

Variables

There is an obvious mismatch between Clojure's immutable data approach, and shell script's mutable variables.

Substitution

For accessing the value of a script variable, stevedore uses deref or @, which are also used for command substitution.

(script (deref TMPDIR))

results in:

${TMPDIR}

If the variable isn't set, a default can be supplied. clojure (script @TMPDIR-/tmp)

results in:

${TMPDIR-/tmp}

Assignment

Values can be defined with set!. clojure (script (set! x 1))

results in:

x=1

Command Substitution

Similar to variable substitution, command substitution uses deref.

(script (set! R @("cat" "/etc/redhat-release")))

results in:

R=$(cat /etc/redhat-release)

Loops

Shell script for loops are mapped to clojure's doseq.

(script
 (doseq [x ["a" "b" "c"]]
   (println @x)))

results in:

for x in a b c; do
  echo ${x}
done

You can also combine command substitution with loops like this

(script
 (doseq [x @("ls" "/home/")]
   (println @x)))
for x in $(ls /home/); do
  echo ${x}
done

Functions

Calling a function follows clojure's normal syntax.

(script ("foo" x y))

results in:

foo x y

Defining a function follows a simplified version of clojure's defn, and does not allow for destructuring. Arguments are named and automatically mapped to the shell function's numbered arguments.

(script (defn foo [x y] ("bar" x)))

results in:

script function foo() {
  x=$1
  y=$2
  bar x
}

Arrays

Array literals are generated from clojure's vector notation.

(script [1 "2" (quoted 3) :four])

results in: bash (1 2 "3" four)

Array access uses aget and aset.

(script (aget foo 1))

results in:

${foo[1]}
(script (aset foo 1 :foo))

results in:

foo[1]=foo

Unquoting

One advantage of embedding script in clojure is that we can escape back to clojure to substitute in values.

(let [tmp "/tmp"]
  (script ("ls" ~tmp)))

results in:

ls /tmp

Expressions

We can also unquote arbitrary clojure expressions. clojure (script ("ls" ~(str "/" "tmp")))

results in:

ls /tmp

Splicing

Sometimes we want to add the contents of a collection into a script, without demarking the boundaries of the collection. clojure (let [files ["a" "b" "c"]] (script ("ls" ~@files)))

results in:

ls a b c

Script functions

Script functions are defined with pallet.script/defscript and provide multimethods for shell script generation. In pallet this is used to abstract away script differences between operating systems.

(require
 'pallet.stevedore.bash)
(use
 '[pallet.script :only [defscript defimpl with-script-context]]
 '[pallet.stevedore :only [script with-script-language]])

(defscript ls [& args])
(defimpl ls :default [& args]
  (ls ~@args))
(defimpl ls [:windows] [& args]
  (dir ~@args))

(with-script-language :pallet.stevedore.bash/bash
  (with-script-context [:windows]
    (script (ls a b c))))

results in:

dir a b c

Chaining

Stevedore offers a few forms for chaining commands:

(script (pipe ("ls") ("grep" bin))) ; ls | grep bin
(script (chain-and ("ls") ("pwd"))) ; ls && pwd
(script (chain-or ("ls") ("pwd"))) ; ls || pwd

Scripting Guide

An excellent reference on shell scripting is available at http://mywiki.wooledge.org/BashGuide.