Using Babashka

Introduction

I never bothered to learn Bash well enough to be fluent with it. If I needed anything beyond basic Bash stuff I immediately used Python for command-line scripting.

In my free time I like to do various cloud and Clojure exercises (if you are interested, check out my personal blog to read more about those exercises: www.karimarttila.fi ). While implementing my latest Clojure exercise I wanted to add a new data store, PostgreSQL. I had previously implemented a DynamoDB data store and implemented importing development data to DynamoDB using Python. This time I wanted to try Babashka to import development data into PostgreSQL - mainly to have an excuse to try if I could replace Python with Clojure for scripting.

This blog post describes my experiences using Babashka. If you want to read the longer story visit my personal blog: Using Clojure in Command Line with Babashka.

What is Babashka?

Babashka is "Clojure for scripting". As the implementor of Babashka, Michiel Borkent, says in the Babashka home page: "The main idea behind babashka is to leverage Clojure in places where you would be using bash otherwise." When running Clojure code with Babashka your script is not compiled to JVM bytecode but executed with a native binary runtime which implements certain core namespaces of the Clojure language. Therefore:

Babashka home page provides a good explanation regarding the Differences with Clojure.

Developing with Babashka

The 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 in the header shows Clojure code in my favorite Clojure IDE, Cursive. This is cool since you can keep your Babashka script in the same project and test the script with the same REPL you are developing your other Clojure code in the same project. For not accidentally calling the script while loading namespaces in the REPL you can e.g. provide an entry point to the script as a top level function call, in the code snippet below: (run-me) - and check if you are calling the function using Babashka.

(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)

Naturally, you can have various function calls in your rich comment in the same namespace to test the functionality you are using when calling by Babashka. (If you are wondering what is a "rich comment" in Clojure, read the excellent explanation in Rich Comment Blocks article by Thomas Mattacchione.)

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 without Babashka but starting a small Clojure script on JVM has certain penalties (if you are interested about the topic, read an excellent article Clojure's slow start — what's inside? by Alexander Yakushev to learn more about it). Not so with Babashka - Babashka boots lightning fast:

λ> time bb '(println "Hello world!")'
Hello world!

real	0m0.006s
user	0m0.003s
sys	0m0.003s

So, where would you use Babashka? Anywhere you would use Bash but you realize that parsing stuff from program output and calling other programs with certain parsed values becomes tedious with Bash - enter Babashka! Call the same programs but this time parse the output with your favorite programming language, Clojure! I used Babashka exactly like this in my exercise: I made a script to read CSV files, parsed them and then called shell commands to insert data into PostgreSQL, e.g.:

(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)]

A bit quick-and-dirty, but does the job.

Clojure (Babashka) vs Python in Shell Scripting

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

Easiness. Both languages are pretty easy and fast to work with if you have used them. Developing Python scripts is pretty fast - you run the script in the terminal. Working with Clojure has one additional plus: you can use the Clojure REPL, which is the most productive tool I have ever used with any language.

Library support: Python wins. When you are scripting in Python and you realize that it would be nice e.g. to use some AWS library - use it. The library support for Babashka is not as extensive, of course - but Babashka supports quite a few namespaces outside of clojure.core and also some additional libraries: Babashka built-in namespaces - keep an eye on that page, Babashka library support may grow in the future!

So, the library support might not be as good as with Python, but I really do love Clojure and if I'm implementing apps using Clojure it is really nice to do some ad hoc scripting using Babashka.

Conclusion

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. At least in my current exercise Babashka did really well.