Using Clojure in Command Line with Babashka

Ordinary Clojure code — can be run in REPL or in command line with Babashka.

Introduction

I never bothered to learn Bash so that I could be really fluent with it. If I needed anything beyond basic Bash stuff I immediately used Python in command-line scripting.

Developing with Babashka

The really neat thing with Babashka is that you can develop your Babashka scripts as part of your Clojure project, or independently but using your favorite Clojure IDE. The picture above shows Clojure code in my favorite Clojure IDE, Cursive. I have the Clojure code that imports data into the Postgres database in directory postgres, so I have the following extra path in my deps.edn:

:postgres {:extra-paths ["postgres"]}
#/bin/bashexport POSTGRES_PASSWORD=simpleserver
export RUNNING_BB=TRUE
bb bb_postgres.clj
(defn run-me
"Loads data only if running from Babashka script which sets the environment variable.
We don't want the repl to load the data when reloading the namespace.
In repl experimentation use the rich comment below."
[]
(let [running-bb? (System/getenv "RUNNING_BB")]
(if (= running-bb? "TRUE")
(import-data))))
(run-me)
(comment
(def data-dir "dev-resources/data")
(get-raw-products data-dir 2)
(get-product-groups data-dir)
(do
(delete-products!)
(delete-product-groups!))
(vals (get-users data-dir))
(load-users (vals (get-users data-dir)))
(import-data)
(db-get-all-product-groups)
(db-get-all-products)
)

Babashka Use Cases

I really like the idea that I can now use Clojure in shell scripting. Of course I could use Clojure in shell scripting also without Babashka but JVM boot takes quite a long time which makes testing of the script in command line a bit painful. Not so with Babashka — Babashka boots lightning fast:

λ> time bb '(println "Hello world!")'
Hello world!
real 0m0.006s
user 0m0.003s
sys 0m0.003s
(defn run-sql [command]
(sh/sh "psql" "--host" "localhost" "--port" "5532" "--username=simpleserver" "--dbname=simpleserver" "-c" command))
(defn insert-product-group! [product-group]
(println "Inserting product-group: " product-group)
(let [[id name] product-group
command (str "INSERT INTO product_group VALUES ('" id "', '" name "');")]
(run-sql command)))
(defn load-product-groups [product-groups]
(doseq [pg product-groups]
(insert-product-group! pg)))
(defn get-product-groups [data-dir]
(let [raw (with-open [reader (io/reader (str data-dir "/product-groups.csv"))]
(doall
(csv/read-csv reader)))
product-groups (into {}
(map
(fn [[item]]
(str/split item #"\t"))
raw))]
product-groups))
(defn import-data []
(let [data-dir "dev-resources/data"
product-groups (get-product-groups data-dir)]
;...
(load-product-groups product-groups)
;...

How Do I Use the Babashka Script in This Exercise?

I used Babashka to load development data into the Postgres data store. During development I built a custom Postgres image and provided a Just recipe to start the data store (file Justfile):

# Start local postgres
@postgres:
cd postgres && ./run-docker-compose.sh
#!/usr/bin/env bashecho "NOTE: Remember to destroy the container if running again!"
echo "Starting docker compose..."
docker-compose -p ss-postgres -f docker-compose-setup-local-postgres.yml up -d
sleep 5
echo "Creating Simple Server schemas..."
./create-schema.sh
sleep 1
echo "Loading data..."
./run-bb-load-data.sh
sleep 1
docker logs -f ss-postgres_postgres_1

Clojure (Babashka) vs Python in Shell Scripting

Let’s finally compare Python and Clojure (Babashka) when doing some Linux shell scripting.

Conclusions

It’s nice to have another scripting tool in my toolbox: Babashka. Time will tell if I start using Clojure instead of Python as my preferred scripting language, thanks to Babashka. At least in this exercise Babashka did really well.

I’m a Software architect and developer. Currently implementing systems on AWS / GCP / Azure / Docker / Kubernetes using Java, Python, Go and Clojure.