A sprinkle of Clojure for the command line.
$ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install)
$ 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)"
6
clojure -e "(+ 1 2 3)" 3.29s user 0.32s system 99% cpu 3.638 total
$ time planck -e '(+ 1 2 3)'
6
plk -e '(+ 1 2 3)' 1.34s user 0.16s system 127% cpu 1.172 total
$ time bb '(+ 1 2 3)'
6
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.
Install:
brew install borkdude/brew/babashka
Upgrade:
brew upgrade babashka
Install via the installer script:
$ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install)
By default this will install into /usr/local/bin
. To change this, provide the directory name:
$ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install) /tmp
You may also download a binary from Github.
$ bb --help
Usage: bb [ --help ] | [ --version ] | ( [ -i ] [ -o ] | [ -io ] ) [ --stream ] ( expression | -f <file> )
Options:
--help: print this help text.
--version: print the current version of babashka.
-i: read shell input into a list of strings instead of reading EDN.
-o: write shell output instead of EDN.
-io: combination of -i and -o.
--stream: stream over lines or EDN values from stdin. Combined with -i *in* becomes a single line per iteration.
--file or -f: read expressions from file instead of argument wrapped in an implicit do.
--time: print execution time before exiting.
The clojure.core
functions are accessible without a namespace alias.
The following Clojure 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
clojure.java.shell
aliases as shell
:
sh
clojure.java.io
aliased as io
:
as-relative-path
, copy
, delete-file
, file
From Java the following is available:
System
: exit
, getProperty
, setProperty
, getProperties
, getenv
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
.Special vars:
*in*
: contains the input read from stdin (EDN by default, multiple lines with the -i
option)*command-line-args*
: contain the command line argsExamples:
$ ls | bb -i '*in*'
["LICENSE" "README.md" "bb" "doc" "pom.xml" "project.clj" "reflection.json" "resources" "script" "src" "target" "test"]
$ ls | bb -i '(count *in*)'
12
$ 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
6
$ ls | bb -i '(filterv #(re-find #"reflection" %) *in*)'
["reflection.json"]
$ 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" ...]
More examples can be found in the gallery.
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 -f
(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 https://www.clojure.org /tmp/clojure.org.html
Fetching url: https://www.clojure.org
Writing file: /tmp/clojure.org.html
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))'
BABASHKA_PRELOADS=$BABASHKA_PRELOADS' (defn bar [x] (* x 2))'
export BABASHKA_PRELOADS
Note that you can concatenate multiple expressions. Now you can use these functions in babashka:
$ bb '(-> (foo *in*) bar)' <<< 1
6
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.
If you want to be able to use SSL to e.g. run (slurp "https://www.clojure.org")
you will need to add the location where
libsunec.so
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.
Example:
$ export BABASHKA_PRELOADS="(System/setProperty \"java.library.path\" \"$JAVA_HOME/jre/lib\")"
$ bb '(slurp "https://www.clojure.org")' | bb '(subs *in* 0 50)'
"<!doctype html><html itemscope=\"\" itemtype=\"http:/"
Test on the JVM:
script/test
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
You will need leiningen and GraalVM.
This repo contains a submodule, so you will have clone that too. If you're doing that for the first time:
$ git submodule update --init --recursive
and for subsequent updates:
$ git submodule update --recursive
To build this project, set $GRAALVM_HOME
to the GraalVM distribution directory.
Then run:
script/compile
Here's a gallery of more useful examples. Do you have a useful example? PR welcome!
$ 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
For converting JSON to EDN, see jet.
$ curl -s https://api.github.com/repos/borkdude/babashka/tags |
jet --from json --keywordize --to edn |
bb '(-> *in* first :name (subs 1))'
"0.0.4"
$ curl -s https://api.github.com/repos/borkdude/babashka/releases |
jet --from json --keywordize |
bb '(-> *in* first :assets)' |
bb '(some #(re-find #".*linux.*" (:browser_download_url %)) *in*)'
"https://github.com/borkdude/babashka/releases/download/v0.0.4/babashka-0.0.4-linux-amd64.zip"
Do you enjoy this project? Consider buying me a hot beverage.
Copyright © 2019 Michiel Borkent
Distributed under the EPL License, same as Clojure. See LICENSE.
Can you improve this documentation? These fine people already did:
Michiel Borkent & Peter StrömbergEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close