A Clojure babushka for the grey areas of Bash.
Life's too short to remember how to write Bash code. I feel liberated.
— @laheadle on Clojurians Slack
The main idea behind babashka is to leverage Clojure in places where you would be using bash otherwise.
As one user described it:
I’m quite at home in Bash most of the time, but there’s a substantial grey area of things that are too complicated to be simple in bash, but too simple to be worth writing a clj/s script for. Babashka really seems to hit the sweet spot for those cases.
- Low latency Clojure scripting alternative to JVM Clojure.
- Easy installation: grab the self-contained binary and run. No JVM needed.
- Familiarity and portability:
- Scripts should be compatible with JVM Clojure as much as possible
- Scripts should be platform-independent as much as possible. Babashka offers support for linux, macOS and Windows.
- Allow interop with commonly used classes like
java.io.File
andSystem
- Multi-threading support (
pmap
,future
,core.async
) - Batteries included (tools.cli, cheshire, ...)
- Library support via popular tools like the
clojure
CLI
- Performance1
- Provide a mixed Clojure/Bash DSL (see portability).
- Replace existing shells. Babashka is a tool you can use inside existing shells like bash and it is designed to play well with them. It does not aim to replace them.
1 Babashka uses sci for interpreting Clojure. Sci implements a suffiently large subset of Clojure. Interpreting code is in general not as performant as executing compiled code. If your script takes more than a few seconds to run, Clojure on the JVM may be a better fit, since the performance of Clojure on the JVM outweighs its startup time penalty. Read more about the differences with Clojure here.
To get an overview of babashka, you can watch this talk (slides):
$ curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install -o install-babashka
$ chmod +x install-babashka && ./install-babashka
$ ls | bb --time -i '(filter #(-> % io/file .isDirectory) *input*)'
("doc" "resources" "sci" "script" "src" "target" "test")
bb took 4ms.
Read the output from a shell command as a lazy seq of strings:
$ ls | bb -i '(take 2 *input*)'
("CHANGES.md" "Dockerfile")
Read EDN from stdin and write the result to stdout:
$ bb '(vec (dedupe *input*))' <<< '[1 1 1 1 2]'
[1 2]
Read more about input and output flags here.
Execute a script. E.g. print the current time in California using the
java.time
API:
File pst.clj
:
#!/usr/bin/env bb
(def now (java.time.ZonedDateTime/now))
(def LA-timezone (java.time.ZoneId/of "America/Los_Angeles"))
(def LA-time (.withZoneSameInstant now LA-timezone))
(def pattern (java.time.format.DateTimeFormatter/ofPattern "HH:mm"))
(println (.format LA-time pattern))
$ pst.clj
05:17
More examples can be found in the gallery.
Functionality regarding clojure.core
and java.lang
can be considered stable
and is unlikely to change. Changes may happen in other parts of babashka,
although we will try our best to prevent them. Always check the release notes or
CHANGES.md before upgrading.
Linux and macOS binaries are provided via brew.
Install:
brew install borkdude/brew/babashka
Upgrade:
brew upgrade babashka
babashka
is available in the Arch User Repository. It can be installed using your favorite AUR helper such as
yay, yaourt, apacman and pacaur. Here is an example using yay
:
yay -S babashka-bin
On Windows you can install using scoop and the scoop-clojure bucket.
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. For linux there is a static binary available which can be used on Alpine.
Check out the image on Docker hub.
Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
[ ( --classpath | -cp ) <cp> ] [ --uberscript <file> ]
[ ( --main | -m ) <main-namespace> | -e <expression> | -f <file> |
--repl | --socket-repl [<host>:]<port> | --nrepl-server [<host>:]<port> ]
[ arg* ]
Options:
--help, -h or -? Print this help text.
--version Print the current version of babashka.
-i Bind *input* to a lazy seq of lines from stdin.
-I Bind *input* to a lazy seq of EDN values from stdin.
-o Write lines to stdout.
-O Write EDN values to stdout.
--verbose Print entire stacktrace in case of exception.
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
--uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
-e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args.
--repl Start REPL. Use rlwrap for history.
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--nrepl-server Start nREPL server. Specify port (e.g. 1667) or host and port separated by colon (e.g. 127.0.0.1:1667).
--time Print execution time before exiting.
-- Stop parsing args and pass everything after -- to *command-line-args*
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 available through the
pre-defined aliases in the user
namespace. You may use require
+ :as
and/or :refer
on these namespaces. If not all vars are available, they are
enumerated explicitly.
clojure.string
aliased asstr
clojure.set
aliased asset
clojure.edn
aliased asedn
:read-string
clojure.java.shell
aliased asshell
clojure.java.io
aliased asio
:as-relative-path
,as-url
,copy
,delete-file
,file
,input-stream
,make-parents
,output-stream
,reader
,resource
,writer
clojure.main
:repl
clojure.core.async
aliased asasync
.clojure.stacktrace
clojure.test
clojure.pprint
:pprint
(currently backed by fipp'sfipp.edn/pprint
)clojure.tools.cli
aliased astools.cli
clojure.data.csv
aliased ascsv
cheshire.core
aliased asjson
cognitect.transit
aliased astransit
clj-yaml.core
alias asyaml
bencode.core
aliased asbencode
:read-bencode
,write-bencode
A selection of java classes are available, see babashka/impl/classes.clj
.
Babashka supports import
: (import clojure.lang.ExceptionInfo)
.
Babashka supports a subset of the ns
form where you may use :require
and :import
:
(ns foo
(:require [clojure.string :as str])
(:import clojure.lang.ExceptionInfo))
For the unsupported parts of the ns form, you may use reader conditionals to maintain compatibility with JVM Clojure.
In one-liners the *input*
value may come in handy. It contains the input read from stdin as EDN by default. If you want to read in text, use the -i
flag, which binds *input*
to a lazy seq of lines of text. If you want to read multiple EDN values, use the -I
flag. The -o
option prints the result as lines of text. The -O
option prints the result as lines of EDN values.
The following table illustrates the combination of options for commands of the form
echo "{{Input}}" | bb {{Input flags}} {{Output flags}} "*input*"
Input | Input flags | Output flag | *input* |
Output |
---|---|---|---|---|
{:a 1} {:a 2} |
{:a 1} |
{:a 1} |
||
hello bye |
-i |
("hello" "bye") |
("hello" "bye") |
|
hello bye |
-i |
-o |
("hello" "bye") |
hello bye |
{:a 1} {:a 2} |
-I |
({:a 1} {:a 2}) |
({:a 1} {:a 2}) |
|
{:a 1} {:a 2} |
-I |
-O |
({:a 1} {:a 2}) |
{:a 1} {:a 2} |
When combined with the --stream
option, the expression is executed for each value in the input:
$ echo '{:a 1} {:a 2}' | bb --stream '*input*'
{:a 1}
{:a 2}
The var *file*
contains the full path of the file that is currently being
executed:
$ cat example.clj
(prn *file*)
$ bb example.clj
"/Users/borkdude/example.clj"
Command-line arguments can be retrieved using *command-line-args*
.
Contains the function add-classpath
which can be used to add to the classpath
dynamically:
(require '[babashka.classpath :refer [add-classpath]]
'[clojure.java.shell :refer [sh]])
(def medley-dep '{:deps {medley {:git/url "https://github.com/borkdude/medley"
:sha "91adfb5da33f8d23f75f0894da1defe567a625c0"}}})
(def cp (:out (sh "clojure" "-Spath" "-Sdeps" (str medley-dep))))
(add-classpath cp)
(require '[medley.core :as m])
(m/index-by :id [{:id 1} {:id 2}]) ;;=> {1 {:id 1}, 2 {:id 2}}
Contains the functions: wait-for-port
and wait-for-path
.
Usage of wait-for-port
:
(wait/wait-for-port "localhost" 8080)
(wait/wait-for-port "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, :default
's value (if any) is returned. The :pause
option determines the time waited between retries.
Usage of wait-for-path
:
(wait/wait-for-path "/tmp/wait-path-test")
(wait/wait-for-path "/tmp/wait-path-test" {:timeout 1000 :pause 1000})
Waits for file path to be available. Options map supports :default
, :timeout
and :pause
. If :timeout
is provided and reached, :default
's value (if any)
is returned. The :pause
option determines the time waited between retries.
The namespace babashka.wait
is aliased as wait
in the user
namespace.
Contains the function signal/pipe-signal-received?
. Usage:
(signal/pipe-signal-received?)
Returns true if PIPE
signal was received. Example:
$ bb '((fn [x] (println x) (when (not (signal/pipe-signal-received?)) (recur (inc x)))) 0)' | head -n2
1
2
The namespace babashka.signal
is aliased as signal
in the user
namespace.
The namespace babashka.curl
is a tiny wrapper around curl. It's aliased as
curl
in the user namespace. See
babashka.curl.
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 https://www.clojure.org /tmp/clojure.org.html
Fetching url: https://www.clojure.org
Writing file: /tmp/clojure.org.html
If /usr/bin/env
doesn't work for you, you can use the following workaround:
$ cat script.clj
#!/bin/sh
#_(
"exec" "bb" "$0" hello "$@"
)
(prn *command-line-args*)
./script.clj 1 2 3
("hello" "1" "2" "3")
Babashka offers a REPL, a socket REPL and an nREPL server. Look here for more information.
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 *input*) bar)' <<< 1
6
You can also preload an entire file using load-file
:
export BABASHKA_PRELOADS='(load-file "my_awesome_prelude.clj")'
Note that *input*
is not available in preloads.
Babashka accepts a --classpath
option that will be used to search for
namespaces and load them:
$ cat src/my/namespace.clj
(ns my.namespace)
(defn -main [& _args]
(println "Hello from my namespace!"))
$ bb --classpath src --main my.namespace
Hello from my namespace!
So if you have a larger script with a classic Clojure project layout like
$ tree -L 3
├── deps.edn
├── README
├── src
│ └── project_namespace
│ ├── main.clj
│ └── utilities.clj
└── test
└── project_namespace
├── test_main.clj
└── test_utilities.clj
Then you can tell Babashka to include both the src
and test
folders in the classpath and start a socket REPL by running:
$ bb --classpath src:test --socket-repl 1666
Note that you can use the clojure
tool to produce classpaths and download dependencies:
$ cat deps.edn
{:deps
{my_gist_script
{:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
:sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}
:aliases {:my-script {:main-opts ["-m" "my-gist-script"]}}}
$ CLASSPATH=$(clojure -Spath)
$ bb --classpath "$CLASSPATH" --main my-gist-script
Hello from gist script!
If there is no --classpath
argument, the BABASHKA_CLASSPATH
environment
variable will be used:
$ export BABASHKA_CLASSPATH=$(clojure -Spath)
$ export BABASHKA_PRELOADS="(require '[my-gist-script])"
$ bb "(my-gist-script/-main)"
Hello from gist script!
When invoking bb
with a main function, the expression (System/getProperty "babashka.main")
will return the name of the main function.
Also see the babashka.classpath namespace which allows dynamically adding to the classpath.
The deps.clj
script can be used to work with deps.edn
-based projects:
$ deps.clj -A:my-script -Scommand "bb -cp {{classpath}} {{main-opts}}"
Hello from gist script!
Create these aliases for brevity:
$ alias bbk='deps.clj -Scommand "bb -cp {{classpath}} {{main-opts}}"'
$ alias babashka='rlwrap deps.clj -Scommand "bb -cp {{classpath}} {{main-opts}}"'
$ bbk -A:my-script
Hello from gist script!
$ babashka
Babashka v0.0.58 REPL.
Use :repl/quit or :repl/exit to quit the REPL.
Clojure rocks, Bash reaches.
user=> (require '[my-gist-script :as mgs])
nil
user=> (mgs/-main)
Hello from gist script!
nil
You can also use for example deps.clj
to produce the classpath for a
babashka
REPL:
$ cat script/start-repl.sh
#!/bin/sh -e
git_root=$(git rev-parse --show-toplevel)
export BABASHKA_CLASSPATH=$("$git_root"/script/deps.clj -Spath)
bb --socket-repl 1666
$ ./script/start-repl.sh
Babashka socket REPL started at localhost:1666
Now, given that your deps.edn
and source tree looks something like
$ cat deps.edn
{:paths ["src" "test"]
:deps {}}
$ tree -L 3
├── deps.edn
├── README
├── script
│ ├── deps.clj
│ └── start-repl.sh
├── src
│ └── project_namespace
│ ├── main.clj
│ └── utilities.clj
└── test
└── project_namespace
├── test_main.clj
└── test_utilities.clj
you should now be able to (require '[multi-machine-rsync.utilities :as util])
in your REPL and the source code in /src/multi_machine_rsync/utilities.clj
will be evaluated and made available through the symbol util
.
The --uberscript
option collects the expressions in
BABASHKA_PRELOADS
, the command line expression or file, the main entrypoint
and all required namespaces from the classpath into a single file. This can be
convenient for debugging and deployment.
Given the deps.edn
from above:
$ deps.clj -A:my-script -Scommand "bb -cp {{classpath}} {{main-opts}} --uberscript my-script.clj"
$ cat my-script.clj
(ns my-gist-script)
(defn -main [& args]
(println "Hello from gist script!"))
(ns user (:require [my-gist-script]))
(apply my-gist-script/-main *command-line-args*)
$ bb my-script.clj
Hello from gist script!
Babashka ships with clojure.tools.cli
:
(require '[clojure.tools.cli :refer [parse-opts]])
(def cli-options
;; An option with a required argument
[["-p" "--port PORT" "Port number"
:default 80
:parse-fn #(Integer/parseInt %)
:validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]]
["-h" "--help"]])
(:options (parse-opts *command-line-args* cli-options))
$ bb script.clj
{:port 80}
$ bb script.clj -h
{:port 80, :help true}
Babashka supports reader conditionals by taking either the :bb
or :clj
branch, whichever comes first. NOTE: the :clj
branch behavior was added in
version 0.0.71, before that version the :clj
branch was ignored.
$ bb "#?(:bb :hello :clj :bye)"
:hello
$ bb "#?(:clj :bye :bb :hello)"
:bye
$ bb "[1 2 #?@(:bb [] :clj [1])]"
[1 2]
Babashka bundles clojure.test
. To make CI scripts fail you can use a simple
runner like this:
#!/usr/bin/env bash
bb -cp "src:test:resources" \
-e "(require '[clojure.test :as t] '[borkdude.deps-test])
(let [{:keys [:fail :error]} (t/run-tests 'borkdude.deps-test)]
(System/exit (+ fail error)))"
Use the java.lang.ProcessBuilder
class.
Example:
user=> (def ws (-> (ProcessBuilder. ["python" "-m" "SimpleHTTPServer" "1777"]) (.start)))
#'user/ws
user=> (wait/wait-for-port "localhost" 1777)
{:host "localhost", :port 1777, :took 2}
user=> (.destroy ws)
nil
Also see this example.
In addition to future
, pmap
, promise
and friends, you may use the
clojure.core.async
namespace for asynchronous scripting. 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
Note: the go
macro is available for compatibility with JVM programs, but the
implementation maps to clojure.core.async/thread
and the single exclamation
mark operations (<!
, >!
, etc.) map to the double exclamation mark operations
(<!!
, >!!
, etc.). It will not "park" threads, like on the JVM.
For making HTTP requests you can use:
- babashka.curl. This library is
included with babashka and aliased as
curl
in the user namespace. slurp
for simpleGET
requests- clj-http-lite as a library.
clojure.java.shell
orjava.lang.ProcessBuilder
for shelling out to your favorite command line http client
This can be useful for talking to Docker:
(require '[clojure.java.shell :refer [sh]])
(require '[cheshire.core :as json])
(-> (sh "curl" "--silent"
"--no-buffer" "--unix-socket"
"/var/run/docker.sock"
"http://localhost/images/json")
:out
(json/parse-string true)
first
:RepoTags) ;;=> ["borkdude/babashka:latest"]
Adding a shutdown hook allows you to execute some code before the script exits.
$ bb -e '(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println "bye"))))'
bye
This also works when the script is interrupted with ctrl-c.
Babashka comes with the nrepl/bencode
library which allows you to read and write bencode messages to a socket. A
simple example which evaluates a Clojure expression on an nREPL server started
with lein repl
:
(ns nrepl-client
(:require [bencode.core :as b]))
(defn nrepl-eval [port expr]
(let [s (java.net.Socket. "localhost" port)
out (.getOutputStream s)
in (java.io.PushbackInputStream. (.getInputStream s))
_ (b/write-bencode out {"op" "eval" "code" expr})
bytes (get (b/read-bencode in) "value")]
(String. bytes)))
(nrepl-eval 52054 "(+ 1 2 3)") ;;=> "6"
Babashka is implemented using the Small Clojure
Interpreter. This means that a snippet or
script is not compiled to JVM bytecode, but executed form by form by a runtime
which implements a sufficiently large subset of Clojure. Babashka is compiled to
a native binary using GraalVM. It comes with
a selection of built-in namespaces and functions from Clojure and other useful
libraries. The data types (numbers, strings, persistent collections) are the
same. Multi-threading is supported (pmap
, future
).
Differences with Clojure:
-
A pre-selected set of Java classes are supported. You cannot add Java classes at runtime.
-
Interpretation comes with overhead. Therefore tight loops are likely slower than in Clojure on the JVM. In general interpretation yields slower programs than compiled programs.
-
No
defprotocol
,defrecord
and unboxed math.
The following libraries are known to work with Babashka:
A port of the clojure bash script to Clojure / babashka.
An babashka-compatible implementation of clojure.spec.alpha
.
This library checks if no assertions have been made in a test:
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {borkdude/missing.test.assertions {:git/url "https://github.com/borkdude/missing.test.assertions" :sha "603cb01bee72fb17addacc53c34c85612684ad70"}}}')
$ lein bb "(require '[missing.test.assertions] '[clojure.test :as t]) (t/deftest foo) (t/run-tests)"
Testing user
WARNING: no assertions made in test foo
Ran 1 tests containing 0 assertions.
0 failures, 0 errors.
{:test 1, :pass 0, :fail 0, :error 0, :type :summary}
Requires bb
>= v0.0.71. Latest coordinates checked with with bb:
{:git/url "https://github.com/weavejester/medley" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}
Example:
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {medley {:git/url "https://github.com/weavejester/medley" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}}}')
$ bb -e "(require '[medley.core :as m]) (m/index-by :id [{:id 1} {:id 2}])"
{1 {:id 1}, 2 {:id 2}}
This fork does not depend on any other libraries. Example:
$ export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {clj-http-lite {:git/url "https://github.com/borkdude/clj-http-lite" :sha "f44ebe45446f0f44f2b73761d102af3da6d0a13e"}}}' -Spath)"
$ bb "(require '[clj-http.lite.client :as client]) (:status (client/get \"https://www.clojure.org\"))"
200
A debug REPL library.
Latest coordinates checked with with bb:
{:git/url "https://github.com/technomancy/limit-break" :sha "050fcfa0ea29fe3340927533a6fa6fffe23bfc2f" :deps/manifest :deps}
Example:
$ export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {limit-break {:git/url "https://github.com/technomancy/limit-break" :sha "050fcfa0ea29fe3340927533a6fa6fffe23bfc2f" :deps/manifest :deps}}}' -Spath)"
$ bb "(require '[limit.break :as lb]) (let [x 1] (lb/break))"
Babashka v0.0.49 REPL.
Use :repl/quit or :repl/exit to quit the REPL.
Clojure rocks, Bash reaches.
break> x
1
A library for reading and writing CSV files. Note that babashka already comes
with clojure.data.csv
, but in case you need this other library, this is how
you can use it:
export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {clojure-csv {:mvn/version "RELEASE"}}}' -Spath)"
./bb -e "
(require '[clojure-csv.core :as csv])
(csv/write-csv (csv/parse-csv \"a,b,c\n1,2,3\"))
"
Requires bb
>= v0.0.71. Latest coordinates checked with with bb:
{:git/url "https://github.com/lambdaisland/regal" :sha "d4e25e186f7b9705ebb3df6b21c90714d278efb7"}
Example:
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {regal {:git/url "https://github.com/lambdaisland/regal" :sha "d4e25e186f7b9705ebb3df6b21c90714d278efb7"}}}')
$ bb -e "(require '[lambdaisland.regal :as regal]) (regal/regex [:* \"ab\"])"
#"(?:\Qab\E)*"
4clojure as a babashka script!
A clojure configuration libary. Latest test version: "0.1.16"
.
Simple templating system for Clojure. Latest tested version: "0.1.1"
.
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {comb {:mvn/version "0.1.1"}}}')
$ rlwrap bb
...
user=> (require '[comb.template :as template])
user=> (template/eval "<% (dotimes [x 3] %>foo<% ) %>")
"foofoofoo"
user=> (template/eval "Hello <%= name %>" {:name "Alice"})
"Hello Alice"
user=> (def hello (template/fn [name] "Hello <%= name %>"))
user=> (hello "Alice")
"Hello Alice"
Docopt implementation in Clojure, compatible with babashka.
Babashka Lambda runtime packaged as a Lambda layer.
Github Action to create a git tag + release when pushed to master. Written in babashka.
A collection of scripts developed by @justone.
Turn babashka scripts into binaries using GraalVM native-image
.
A micro-library which provides a total-ordering comparator for Clojure
values. Tested with version 1.2.0
.
AWS Lambda runtime doesn't support signals, therefore babashka has to disable
handling of the SIGPIPE. This can be done by setting
BABASHKA_DISABLE_PIPE_SIGNAL_HANDLER
to true
.
- Implementing an nREPL server for babashka: impromptu presentation by Michiel Borkent at the online Dutch Clojure Meetup
- ClojureScript podcast with Jacek Schae interviewing Michiel Borkent
- Babashka talk at ClojureD (slides) by Michiel Borkent
- Babashka: a quick example by Malcolm Sparks
- Clojure Start Time in 2019 by Stuart Sierra
- Advent of Random Hacks by Arne Brasseur
- Clojure in the Shell by Arne Brasseur
- Clojure Tool by Eric Normand
Here's a gallery of more useful examples. Do you have a useful example? PR welcome!
find . | grep conflict | bb -i '(doseq [f *input*] (.delete (io/file f)))'
#!/usr/bin/env bb
(as-> (io/file (or (first *command-line-args*) ".")) $
(file-seq $)
(map #(.length %) $)
(reduce + $)
(/ $ (* 1024 1024))
(println (str (int $) "M")))
$ dir-size
130M
$ dir-size ~/Dropbox/bin
233M
$ cat /tmp/test.txt
1 Hello
2 Clojure
3 Babashka
4 Goodbye
$ < /tmp/test.txt bb -io '(shuffle *input*)'
3 Babashka
2 Clojure
4 Goodbye
1 Hello
(require '[clojure.java.shell :refer [sh]]
'[cheshire.core :as json])
(defn babashka-latest-version []
(-> (sh "curl" "https://api.github.com/repos/borkdude/babashka/tags")
:out
(json/parse-string true)
first
:name))
(babashka-latest-version) ;;=> "v0.0.73"
#!/usr/bin/env bb
(require '[clojure.java.shell :refer [sh]]
'[clojure.string :as str])
(let [[username project branch] *command-line-args*
branch (or branch "master")
url (str "https://github.com/" username "/" project)
sha (-> (sh "git" "ls-remote" url branch)
:out
(str/split #"\s")
first)]
{:git/url url
:sha sha})
$ gitlib.clj nate fs
{:git/url "https://github.com/nate/fs", :sha "75b9fcd399ac37cb4f9752a4c7a6755f3fbbc000"}
$ clj -Sdeps "{:deps {fs $(gitlib.clj nate fs)}}" \
-e "(require '[nate.fs :as fs]) (fs/creation-time \".\")"
#object[java.nio.file.attribute.FileTime 0x5c748168 "2019-07-05T14:06:26Z"]
Contributed by @plexus.
$ curl https://clojars.org/stats/all.edn |
bb -o '(for [[[group art] counts] *input*] (str (reduce + (vals counts)) " " group "/" art))' |
sort -rn |
less
14113842 clojure-complete/clojure-complete
9065525 clj-time/clj-time
8504122 cheshire/cheshire
...
See examples/tree.clj.
$ clojure -Sdeps '{:deps {org.clojure/tools.cli {:mvn/version "0.4.2"}}}' examples/tree.clj src
src
└── babashka
├── impl
│ ├── tools
│ │ └── cli.clj
...
$ examples/tree.clj src
src
└── babashka
├── impl
│ ├── tools
│ │ └── cli.clj
...
See examples/outdated.clj. Inspired by an idea from @seancorfield.
$ cat /tmp/deps.edn
{:deps {cheshire {:mvn/version "5.8.1"}
clj-http {:mvn/version "3.4.0"}}}
$ examples/outdated.clj /tmp/deps.edn
clj-http/clj-http can be upgraded from 3.4.0 to 3.10.0
cheshire/cheshire can be upgraded from 5.8.1 to 5.9.0
Contributed by @plexus.
$ cat project.clj |
sed -e 's/#=//g' -e 's/~@//g' -e 's/~//g' |
bb '(let [{:keys [dependencies source-paths resource-paths]} (apply hash-map (drop 3 *input*))]
{:paths (into source-paths resource-paths)
:deps (into {} (for [[d v] dependencies] [d {:mvn/version v}]))}) ' |
jet --pretty > deps.edn
A script with the same goal can be found here.
See examples/pst.clj
Original by @souenzzo
$ examples/random_doc.clj
-------------------------
clojure.core/ffirst
([x])
Same as (first (first x))
sha1.clj
:
#!/usr/bin/env bb
(defn sha1
[s]
(let [hashed (.digest (java.security.MessageDigest/getInstance "SHA-1")
(.getBytes s))
sw (java.io.StringWriter.)]
(binding [*out* sw]
(doseq [byte hashed]
(print (format "%02X" byte))))
(str sw)))
(sha1 (first *command-line-args*))
$ sha1.clj babashka
"0AB318BE3A646EEB1E592781CBFE4AE59701EDDF"
Dockerfile
:
FROM borkdude/babashka
RUN echo $'\
(println "Your command line args:" *command-line-args*)\
'\
>> script.clj
ENTRYPOINT ["bb", "script.clj"]
$ docker build . -t script
...
$ docker run --rm script 1 2 3
Your command line args: (1 2 3)
;; Given the following:
;; $ echo 'contents' > file
;; $ zip zipfile.zip file
;; $ rm file
;; we extract the single file from the zip archive using java.nio:
(import '[java.nio.file Files FileSystems CopyOption])
(let [zip-file (io/file "zipfile.zip")
file (io/file "file")
fs (FileSystems/newFileSystem (.toPath zip-file) nil)
file-in-zip (.getPath fs "file" (into-array String []))]
(Files/copy file-in-zip (.toPath file)
(into-array CopyOption [])))
See
examples/notes.clj. This
is a variation on the
http-server
example. If you get prompted with a login, use admin
/admin
.
The which
command re-implemented in Clojure. See
examples/which.clj.
Prints the canonical file name.
$ examples/which.clj rg
/usr/local/Cellar/ripgrep/11.0.1/bin/rg
- adgoji for financial support
- CircleCI for CI and additional support
- Nikita Prokopov for the logo
- contributors and other users posting issues with bug reports and ideas
This project exists thanks to all the people who contribute. [Contribute].
Become a financial contributor and help us sustain our community. [Contribute]
Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]
Copyright © 2019-2020 Michiel Borkent
Distributed under the EPL License. See LICENSE.
This project contains code from:
- Clojure, which is licensed under the same EPL License.