# babashka [![CircleCI](https://circleci.com/gh/borkdude/babashka/tree/master.svg?style=shield)](https://circleci.com/gh/borkdude/babashka/tree/master) [![Clojars Project](https://img.shields.io/clojars/v/borkdude/babashka.svg)](https://clojars.org/borkdude/babashka) [![cljdoc badge](https://cljdoc.org/badge/borkdude/babashka)](https://cljdoc.org/d/borkdude/babashka/CURRENT) A sprinkle of Clojure for the command line. ## Quickstart ``` shellsession $ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install) $ bb '(vec (dedupe *in*))' <<< '[1 1 1 1 2]' [1 2] ``` ## Rationale 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](https://github.com/oracle/graal) binary which results in fast startup times: ``` shellsession $ 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](https://github.com/borkdude/sci) for interpreting Clojure. A trade-off is that [sci](https://github.com/borkdude/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: ``` shellsession (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 JVM packages like `System`, so writing code in it should be familiar if you're already using Clojure on the JVM. ## Status Experimental. Breaking changes are expected to happen at this phase. ## Installation ### Brew Linux and macOS binaries are provided via brew. Install: brew install borkdude/brew/babashka Upgrade: brew upgrade babashka ### Installer script Install via the installer script: ``` shellsession $ 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: ``` shellsession $ bash <(curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install) /tmp ``` ### Download You may also download a binary from [Github](https://github.com/borkdude/babashka/releases). ## Usage ``` shellsession bb [ --help ] | [ --version ] | ( [ -i ] [ -o ] | [ -io ] ) [ --stream ] ( expression | -f ) ``` Type `bb --help` to see a full explanation of the options. The `clojure.core` functions are accessible without a namespace alias. The following Clojure namespaces are required by default and only available through the aliases: - `clojure.string` aliased as `str` - `clojure.set` aliased as `set` - `clojure.edn` aliased as `edn` (only `read-string` is available) - `clojure.java.shell` aliases as `shell` (only `sh` is available) From Java the following is available: - `System`: `exit`, `getProperty`, `setProperty`, `getProperties`, `getenv` 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 args Examples: ``` shellsession $ 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}] ``` ``` shellsession $ bb '(#(+ %1 %2 %3) 1 2 *in*)' <<< 3 6 ``` ``` shellsession $ ls | bb -i '(filterv #(re-find #"reflection" %) *in*)' ["reflection.json"] ``` ``` shellsession $ 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](#gallery). ## Running a file Scripts may be executed from a file using `-f` or `--file`: ``` shellsession bb -f download_html.clj ``` Files can also be loaded inline using `load-file`: ``` shellsession bb '(load-file "script.clj")' ``` Using `bb` with a shebang also works: ``` clojure #!/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: ") (System/exit 1)) (write-html file (get-url url))) (System/exit 0) ``` ``` shellsession $ ./download_html.clj Usage: $ ./download_html.clj https://www.clojure.org /tmp/clojure.org.html Fetching url: https://www.clojure.org Writing file: /tmp/clojure.org.html ``` ## Enabling SSL 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 `/jre/lib` or `/jre//lib`. It is also bundled with GraalVM. Example: ``` shellsession $ cat /tmp/https_get.clj #!/usr/bin/env bb -f (System/setProperty "java.library.path" "/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/jre/lib") (slurp (first *command-line-args*)) ``` ``` shellsession $ /tmp/https_get.clj https://www.google.com | bb '(subs *in* 0 50)' " *in* first :name (subs 1))' "0.0.4" ``` ### Get latest OS-specific download url from Github ``` shellsession $ 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" ``` ## Support this project Do you enjoy this project? Consider buying me a [hot beverage](https://ko-fi.com/borkdude). ## License Copyright © 2019 Michiel Borkent Distributed under the EPL License, same as Clojure. See LICENSE.