Liking cljdoc? Tell your friends :D


CircleCI Clojars Project cljdoc badge

A sprinkle of Clojure for the command line.


$ bash <(curl -s
$ ls | bb --time -i '(filter #(-> % io/file .isDirectory) *in*)'
("doc" "resources" "sci" "script" "src" "target" "test")
bb took 4ms.


If you're a bash expert, you probably don't need this. But for those of us who can use a bit of Clojure in their shell scripts, it may be useful.

Babashka runs as a GraalVM binary which results in fast startup times:

$ time clojure -e "(+ 1 2 3)"
clojure -e "(+ 1 2 3)"  3.29s user 0.32s system 99% cpu 3.638 total

$ time planck -e '(+ 1 2 3)'
plk -e '(+ 1 2 3)'  1.34s user 0.16s system 127% cpu 1.172 total

$ time bb '(+ 1 2 3)'
bb '(+ 1 2 3)'  0.01s user 0.01s system 37% cpu 0.046 total

It uses sci for interpreting Clojure. A trade-off is that sci implements only a subset of Clojure. Also, execution time may be slower than Clojure on the JVM or (self-hosted) ClojureScript for more CPU-intensive calculations like:

(last (take 1000000 (repeatedly #(+ 1 2 3))))

This would take 5 seconds using babashka, around half a second using self-hosted ClojureScript and around 200ms in Clojure on the JVM.

So the sweet spot for babashka is executing tasks from the command line where fast startup time is preferred, in the same space where you would use bash.

Where it can, babashka calls the regular implementation of Clojure on the JVM and proxies common Java packages like System and File, so writing code in it should be familiar if you're already using Clojure on the JVM.


Experimental. Breaking changes are expected to happen at this phase.



Linux and macOS binaries are provided via brew.


brew install borkdude/brew/babashka


brew upgrade babashka

Installer script

Install via the installer script:

$ bash <(curl -s

By default this will install into /usr/local/bin. To change this, provide the directory name:

$ bash <(curl -s /tmp


You may also download a binary from Github.


Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] ( -e <expression> | -f <file> | --socket-repl [<host>:]<port> )


  --help, -h or -?: print this help text.
  --version: print the current version of babashka.

  -i: bind *in* to a lazy seq of lines from stdin.
  -I: bind *in* to a lazy seq of EDN values from stdin.
  -o: write lines to stdout.
  -O: write EDN values to stdout.
  --stream: stream over lines or EDN values from stdin. Combined with -i or -I *in* becomes a single value per iteration.
  -e, --eval <expression>: evaluate an expression
  -f, --file <path>: evaluate a file
  --socket-repl: start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g.
  --time: print execution time before exiting.

If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise.
Everything after that is bound to *command-line-args*.

The clojure.core functions are accessible without a namespace alias.

The following namespaces are required by default and only available through the aliases. If not all vars are available, they are enumerated explicitly.

  • clojure.string aliased as str
  • clojure.set aliased as set
  • clojure.edn aliased as edn:
    • read-string
  • aliases as shell:
    • sh
  • aliased as io:
    • as-relative-path, copy, delete-file, file
  • clojure.core.async aliased as async. The alt and go macros are not available but alts!! does work as it is a function.
  • me.raynes.conch.low-level aliased as conch

From Java the following is available:

  • File: .canRead, .canWrite, .delete, .deleteOnExit, .exists, .getAbsoluteFile, .getCanonicalFile, .getCanonicalPath, .getName, .getParent, .getParentFile, .getPath, .isAbsolute, .isDirectory, .isFile, .isHidden, .lastModified, .length, .list, .listFiles, .mkdir, .mkdirs, .renameTo, .setLastModified, .setReadOnly, .setReadable, .toPath, .toURI.
  • System: exit, getProperty, setProperty, getProperties, getenv
  • Thread: sleep

Special vars:

  • *in*: contains the input read from stdin. EDN by default, multiple lines of text with the -i option, or multiple EDN values with the -I option.
  • *command-line-args*: contain the command line args

Additionally, babashka adds the following functions:

  • net/wait-for-it. Usage:
(net/wait-for-it "localhost" 8080)
(net/wait-for-it "localhost" 8080 {:timeout 1000 :pause 1000)

Waits for TCP connection to be available on host and port. Options map supports :timeout and :pause. If :timeout is provided and reached, exception will be thrown. The :pause option determines the time waited between retries.

  • sig/pipe-signal-received?. Usage:

Returns true if PIPE signal was received. Example:

$ bb '((fn [x] (println x) (when (not (sig/pipe-signal-received?)) (recur (inc x)))) 0)' | head -n2


$ ls | bb -i '*in*'
["LICENSE" "" "bb" "doc" "pom.xml" "project.clj" "reflection.json" "resources" "script" "src" "target" "test"]

$ ls | bb -i '(count *in*)'

$ bb '(vec (dedupe *in*))' <<< '[1 1 1 1 2]'
[1 2]

$ bb '(filterv :foo *in*)' <<< '[{:foo 1} {:bar 2}]'
[{:foo 1}]

$ bb '(#(+ %1 %2 %3) 1 2 *in*)' <<< 3

$ ls | bb -i '(filterv #(re-find #"reflection" %) *in*)'

$ bb '(run! #(shell/sh "touch" (str "/tmp/test/" %)) (range 100))'
$ ls /tmp/test | bb -i '*in*'
["0" "1" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "2" "20" "21" ...]

$ bb -O '(repeat "dude")' | bb --stream '(str *in* "rino")' | bb -I '(take 3 *in*)'
("duderino" "duderino" "duderino")

More examples can be found in the gallery.

Running a file

Scripts may be executed from a file using -f or --file:

bb -f download_html.clj

Files can also be loaded inline using load-file:

bb '(load-file "script.clj")'

Using bb with a shebang also works:

#!/usr/bin/env bb

(defn get-url [url]
  (println "Fetching url:" url)
  (let [{:keys [:exit :err :out]} (shell/sh "curl" "-sS" url)]
    (if (zero? exit) out
      (do (println "ERROR:" err)
          (System/exit 1)))))

(defn write-html [file html]
  (println "Writing file:" file)
  (spit file html))

(let [[url file] *command-line-args*]
  (when (or (empty? url) (empty? file))
    (println "Usage: <url> <file>")
    (System/exit 1))
  (write-html file (get-url url)))

(System/exit 0)
$ ./download_html.clj
Usage: <url> <file>

$ ./download_html.clj /tmp/
Fetching url:
Writing file: /tmp/

If /usr/bin/env doesn't work for you, you can use the following workaround:

$ cat script.clj

   "exec" "bb" "$0" hello "$@"

(prn *command-line-args*)

./script.clj 1 2 3
("hello" "1" "2" "3")


The environment variable BABASHKA_PRELOADS allows to define code that will be available in all subsequent usages of babashka.

BABASHKA_PRELOADS='(defn foo [x] (+ x 2))'

Note that you can concatenate multiple expressions. Now you can use these functions in babashka:

$ bb '(-> (foo *in*) bar)' <<< 1

You can also preload an entire file using load-file:

export BABASHKA_PRELOADS='(load-file "my_awesome_prelude.clj")'

Note that *in* is not available in preloads.

Socket REPL

Start the socket REPL like this:

$ bb --socket-repl 1666
Babashka socket REPL started at localhost:1666

Now you can connect with your favorite socket REPL client:

$ rlwrap nc 1666
Babashka v0.0.14 REPL.
Use :repl/quit or :repl/exit to quit the REPL.
Clojure rocks, Bash reaches.

bb=> (+ 1 2 3)
bb=> :repl/quit

A socket REPL client for Emacs is inf-clojure.

Spawning and killing a process

You may use the conch namespace for this. It maps to me.raynes.conch.low-level.


$ bb '
(def ws (conch/proc "python" "-m" "SimpleHTTPServer" "1777"))
(net/wait-for-it "localhost" 1777) (conch/destroy ws)'


Apart from future for creating threads and the conch namespace for creating processes, you may use core.async to script asynchronously. The following example shows how to get first available value from two different processes:

bb '
(defn async-command [& args]
  (async/thread (apply shell/sh "bash" "-c" args)))

(-> (async/alts!! [(async-command "sleep 2 && echo process 1")
                   (async-command "sleep 1 && echo process 2")])
    first :out str/trim println)'
process 2

Enabling SSL

If you want to be able to use SSL to e.g. run (slurp "") you will need to add the location where or libsunec.dylib is located to the java.library.path Java property. This library comes with most JVM installations, so you might already have it on your machine. It is usually located in <JAVA_HOME>/jre/lib or <JAVA_HOME>/jre/<platform>/lib. It is also bundled with GraalVM.


$ export BABASHKA_PRELOADS="(System/setProperty \"java.library.path\" \"$JAVA_HOME/jre/lib\")"
$ bb '(slurp "")' | bb '(subs *in* 0 50)'
"<!doctype html><html itemscope=\"\" itemtype=\"http:/"

Developing Babashka

To work on Babashka itself make sure Git submodules are checked out.

$ git clone --recursive

To update later on:

$ git submodule update --recursive

You need Leiningen, and for building binaries you need GraalVM.


lein repl will get you a standard REPL/nREPL connection. To work on tests use lein with-profiles +test repl.


Test on the JVM:


Although this tool doesn't offer any benefit when running on the JVM, it is convenient for development.

Test the native version:

BABASHKA_TEST_ENV=native script/test


To build this project, set $GRAALVM_HOME to the GraalVM distribution directory.

Then run:


Related projects


Here's a gallery of more useful examples. Do you have a useful example? PR welcome!

Shuffle the lines of a file

$ cat /tmp/test.txt
1 Hello
2 Clojure
3 Babashka
4 Goodbye

$ < /tmp/test.txt bb -io '(shuffle *in*)'
3 Babashka
2 Clojure
4 Goodbye
1 Hello

Fetch latest Github release tag

For converting JSON to EDN, see jet.

$ curl -s |
jet --from json --keywordize --to edn |
bb '(-> *in* first :name (subs 1))'

Get latest OS-specific download url from Github

$ curl -s |
jet --from json --keywordize |
bb '(-> *in* first :assets)' |
bb '(some #(re-find #".*linux.*" (:browser_download_url %)) *in*)'


Copyright © 2019 Michiel Borkent

Distributed under the EPL License. See LICENSE.

This project contains code from:

  • Clojure, which is licensed under the same EPL License.
  • conch, which is licensed under the same EPL License.

Can you improve this documentation? These fine people already did:
Michiel Borkent, Arne Brasseur & Peter Strömberg
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close