2019-12-30 08:51:20 +00:00
< img src = "logo/babashka.svg" width = "425px" >
2019-08-09 12:51:42 +00:00
[](https://circleci.com/gh/borkdude/babashka/tree/master)
[](https://clojars.org/borkdude/babashka)
2019-12-12 11:18:24 +00:00
[](https://app.slack.com/client/T03RZGPFR/CLX41ASCS)
2019-12-19 13:01:14 +00:00
<!-- [](https://cljdoc.org/d/borkdude/babashka/CURRENT) -->
2019-08-09 12:51:42 +00:00
2019-11-07 11:46:14 +00:00
A Clojure [babushka ](https://en.wikipedia.org/wiki/Headscarf ) for the grey areas of Bash.
2019-08-12 12:42:45 +00:00
2019-12-20 15:51:41 +00:00
< blockquote class = "twitter-tweet" data-lang = "en" >
2019-12-30 12:38:05 +00:00
< p lang = "en" dir = "ltr" > Life's too short to remember how to write Bash code. I feel liberated.< / p >
2019-12-20 15:51:41 +00:00
—
< a href = "https://github.com/laheadle" > @laheadle< / a > on Clojurians Slack
< / blockquote >
2020-03-02 08:50:47 +00:00
The sweet spot for babashka is executing Clojure expressions or scripts in the
same space where you would use Bash.
2019-08-15 09:05:59 +00:00
2019-11-07 11:25:51 +00:00
As one user described it:
2019-11-07 11:46:14 +00:00
> 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.
2019-11-07 11:25:51 +00:00
2020-03-02 09:01:04 +00:00
## Goals
2019-11-03 20:28:16 +00:00
2020-03-02 08:50:47 +00:00
* Low latency Clojure scripting alternative to JVM Clojure.
2020-03-02 08:55:50 +00:00
* Easy installation: grab the self-contained binary and run. No JVM needed.
2020-03-02 08:50:47 +00:00
* Familiarity and portability:
- Scripts should be compatible with JVM Clojure as much as possible
2020-03-02 08:55:50 +00:00
- Scripts should be platform-independent as much as possible. Babashka offers
support for linux, macOS and Windows.
2020-03-02 08:50:47 +00:00
* Allow interop with commonly used classes like `java.io.File` and `System`
* Multi-threading support (`pmap`, `future` , `core.async` )
* Batteries included (tools.cli, cheshire, ...)
* Library support via popular tools like the `clojure` CLI
2019-11-03 20:28:16 +00:00
2020-03-02 09:02:36 +00:00
Also see the [slides ](https://speakerdeck.com/borkdude/babashka-and-the-small-clojure-interpreter-at-clojured-2020 ) of the Babashka talk at ClojureD 2020 (video coming soon).
2020-03-02 09:01:04 +00:00
## Non-goals
2019-11-03 20:28:16 +00:00
2020-03-02 08:56:22 +00:00
* Performance< sup > 1< sup >
* 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.
< sup > 1< sup > Babashka uses [sci ](https://github.com/borkdude/sci ) for
2020-03-02 08:55:50 +00:00
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
2020-03-02 08:57:24 +00:00
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
2020-03-02 08:55:50 +00:00
[here ](#differences-with-clojure ).
2019-08-17 22:31:39 +00:00
2020-03-02 09:00:28 +00:00
## Quickstart
``` shellsession
$ bash < (curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install)
$ ls | bb --time -i '(filter #(-> % io/file .isDirectory) *input* )'
("doc" "resources" "sci" "script" "src" "target" "test")
bb took 4ms.
```
2019-09-08 21:31:24 +00:00
## Status
2020-03-02 12:49:23 +00:00
Functionality regarding `clojure.core` and `java.lang` can be considered stable
and is unlikely to change. Changes may happen in other parts of babashka,
2020-03-02 12:53:28 +00:00
although we will try our best to prevent them. Always check the release notes or
2020-03-02 12:52:36 +00:00
[CHANGES.md ](CHANGES.md ) before upgrading.
2019-09-08 21:31:24 +00:00
## Examples
2019-08-17 22:31:39 +00:00
``` shellsession
2019-12-20 22:51:24 +00:00
$ ls | bb -i '*input*'
2020-01-04 19:06:11 +00:00
["LICENSE" "README.md" "bb" "doc" "pom.xml" "project.clj" "resources" "script" "src" "target" "test"]
2019-08-17 22:31:39 +00:00
2019-12-20 22:51:24 +00:00
$ ls | bb -i '(count *input* )'
2019-09-08 21:31:24 +00:00
12
2019-08-17 22:31:39 +00:00
2019-12-20 22:51:24 +00:00
$ bb '(vec (dedupe *input* ))' <<< '[1 1 1 1 2]'
2019-09-08 21:31:24 +00:00
[1 2]
2019-08-15 09:16:15 +00:00
2019-12-20 22:51:24 +00:00
$ bb '(filterv :foo *input* )' <<< '[{:foo 1} {:bar 2}]'
2019-09-08 21:31:24 +00:00
[{:foo 1}]
2019-08-18 05:30:29 +00:00
2019-12-20 22:51:24 +00:00
$ bb '(#(+ %1 %2 %3) 1 2 *input* )' << < 3
2019-09-08 21:31:24 +00:00
6
2019-08-09 12:51:42 +00:00
2020-01-04 19:06:11 +00:00
$ ls | bb -i '(filterv #(re-find #"README" %) *input* )'
["README.md"]
2019-09-08 21:31:24 +00:00
$ bb '(run! #(shell/sh "touch" (str "/tmp/test/" %)) (range 100))'
2019-12-20 22:51:24 +00:00
$ ls /tmp/test | bb -i '*input*'
2019-09-08 21:31:24 +00:00
["0" "1" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "2" "20" "21" ...]
2019-12-20 22:51:24 +00:00
$ bb -O '(repeat "dude")' | bb --stream '(str *input* "rino")' | bb -I '(take 3 *input* )'
2019-09-08 21:31:24 +00:00
("duderino" "duderino" "duderino")
```
More examples can be found in the [gallery ](#gallery ).
2019-08-09 12:51:42 +00:00
2019-08-09 14:02:08 +00:00
## Installation
2019-08-11 07:27:45 +00:00
### Brew
2019-08-09 14:02:08 +00:00
Linux and macOS binaries are provided via brew.
Install:
brew install borkdude/brew/babashka
Upgrade:
brew upgrade babashka
2019-11-21 10:14:38 +00:00
### Arch (Linux)
`babashka` is [available ](https://aur.archlinux.org/packages/babashka-bin/ ) in the [Arch User Repository ](https://aur.archlinux.org ). It can be installed using your favorite [AUR ](https://aur.archlinux.org ) helper such as
[yay ](https://github.com/Jguer/yay ), [yaourt ](https://github.com/archlinuxfr/yaourt ), [apacman ](https://github.com/oshazard/apacman ) and [pacaur ](https://github.com/rmarquis/pacaur ). Here is an example using `yay` :
yay -S babashka-bin
2019-08-11 07:27:45 +00:00
2020-02-02 12:44:34 +00:00
### Windows
On Windows you can install using [scoop ](https://scoop.sh/ ) and the
[scoop-clojure ](https://github.com/littleli/scoop-clojure ) bucket.
2019-08-11 07:27:45 +00:00
### 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
2020-02-23 21:56:47 +00:00
You may also download a binary from
[Github ](https://github.com/borkdude/babashka/releases ). For linux there is a
static binary available which can be used on Alpine.
2019-08-09 14:02:08 +00:00
2019-08-09 13:48:54 +00:00
## Usage
2019-08-09 12:51:42 +00:00
2019-08-09 17:58:11 +00:00
``` shellsession
2019-12-13 17:16:13 +00:00
Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
2020-01-01 22:27:25 +00:00
[ ( --classpath | -cp ) < cp > ] [ --uberscript < file > ]
[ ( --main | -m ) < main-namespace > | -e < expression > | -f < file > |
--repl | --socket-repl [< host > :]< port > ]
[ arg* ]
2019-08-18 10:33:54 +00:00
Options:
2019-08-09 17:58:11 +00:00
2019-12-28 21:52:56 +00:00
--help, -h or -? Print this help text.
--version Print the current version of babashka.
2020-01-01 22:27:25 +00:00
2019-12-28 21:52:56 +00:00
-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.
2020-01-01 22:27:25 +00:00
--uberscript < file > Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
2019-12-28 21:52:56 +00:00
-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.
2020-02-07 08:33:02 +00:00
--repl Start REPL. Use rlwrap for history.
2019-12-28 21:52:56 +00:00
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--time Print execution time before exiting.
2020-01-31 16:34:39 +00:00
-- Stop parsing args and pass everything after -- to *command-line-args*
2019-09-06 14:40:50 +00:00
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* .
2019-08-18 10:33:54 +00:00
```
2019-08-14 11:38:39 +00:00
The `clojure.core` functions are accessible without a namespace alias.
2019-09-08 21:31:24 +00:00
The following namespaces are required by default and available through the
2019-12-10 08:44:41 +00:00
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.
2019-08-14 11:38:39 +00:00
- `clojure.string` aliased as `str`
- `clojure.set` aliased as `set`
2019-08-18 10:33:54 +00:00
- `clojure.edn` aliased as `edn` :
- `read-string`
2020-01-25 16:00:40 +00:00
- `clojure.java.shell` aliased as `shell`
2019-08-18 10:33:54 +00:00
- `clojure.java.io` aliased as `io` :
2019-12-24 09:01:32 +00:00
- `as-relative-path` , `as-url` , `copy` , `delete-file` , `file` , `input-stream` ,
2019-12-29 22:37:57 +00:00
`make-parents` , `output-stream` , `reader` , `resource` , `writer`
2019-12-24 09:01:32 +00:00
- `clojure.main` : `repl`
2019-09-07 08:43:53 +00:00
- [`clojure.core.async` ](https://clojure.github.io/core.async/ ) aliased as
2020-03-01 15:56:28 +00:00
`async` .
2020-01-02 12:53:35 +00:00
- `clojure.stacktrace`
2020-01-29 22:53:01 +00:00
- `clojure.test`
2020-02-19 22:22:49 +00:00
- `clojure.pprint` : `pprint` (currently backed by [fipp ](https://github.com/brandonbloom/fipp )'s `fipp.edn/pprint` )
2019-09-08 21:07:58 +00:00
- [`clojure.tools.cli` ](https://github.com/clojure/tools.cli ) aliased as `tools.cli`
2019-11-11 20:14:30 +00:00
- [`clojure.data.csv` ](https://github.com/clojure/data.csv ) aliased as `csv`
2019-12-10 08:44:41 +00:00
- [`cheshire.core` ](https://github.com/dakrone/cheshire ) aliased as `json`
2019-08-14 11:38:39 +00:00
2020-01-04 19:06:11 +00:00
A selection of java classes are available, see `babashka/impl/classes.clj` .
2019-12-31 13:23:54 +00:00
Babashka supports `import` : `(import clojure.lang.ExceptionInfo)` .
2019-11-27 13:15:29 +00:00
Babashka supports a subset of the `ns` form where you may use `:require` and `:import` :
``` shellsession
(ns foo
(:require [clojure.string :as str])
(:import clojure.lang.ExceptionInfo))
2019-11-15 23:25:36 +00:00
```
2019-11-27 13:15:29 +00:00
For the unsupported parts of the ns form, you may use [reader
conditionals](#reader-conditionals) to maintain compatibility with JVM Clojure.
2019-12-20 20:17:27 +00:00
### Input and output flags
2019-08-15 04:28:00 +00:00
2019-12-20 22:51:24 +00:00
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.
2019-12-20 20:17:27 +00:00
The following table illustrates the combination of options for commands of the form
2019-12-20 22:51:24 +00:00
echo "{{Input}}" | bb {{Input flags}} {{Output flags}} "*input*"
2019-12-20 20:17:27 +00:00
2019-12-20 22:51:24 +00:00
| Input | Input flags | Output flag | `*input*` | Output |
2019-12-20 20:17:27 +00:00
|----------------|-------------|-------------|---------------|----------|
2019-12-20 20:23:51 +00:00
| `{:a 1}` < br > `{:a 2}` | | | `{:a 1}` | `{:a 1}` |
2019-12-20 20:17:27 +00:00
| hello < br > bye | `-i` | | `("hello" "bye")` | `("hello" "bye")` |
| hello < br > bye | `-i` | `-o` | `("hello" "bye")` | hello < br > bye |
2019-12-20 20:23:51 +00:00
| `{:a 1}` < br > `{:a 2}` | `-I` | | `({:a 1} {:a 2})` | `({:a 1} {:a 2})` |
2019-12-20 20:17:27 +00:00
| `{:a 1}` < br > `{:a 2}` | `-I` | `-O` | `({:a 1} {:a 2})` | `{:a 1}` < br > `{:a 2}` |
2019-12-20 23:06:00 +00:00
When combined with the `--stream` option, the expression is executed for each value in the input:
2019-12-20 20:17:27 +00:00
``` clojure
2019-12-20 22:51:24 +00:00
$ echo '{:a 1} {:a 2}' | bb --stream '*input*'
2019-12-20 20:17:27 +00:00
{:a 1}
{:a 2}
```
2019-12-21 10:39:28 +00:00
### Current file path
2019-12-21 10:50:05 +00:00
The var `*file*` contains the full path of the file that is currently being
executed:
2019-12-21 10:39:28 +00:00
``` shellsession
$ cat example.clj
(prn *file* )
$ bb example.clj
"/Users/borkdude/example.clj"
```
2019-12-20 20:17:27 +00:00
### Command-line arguments
Command-line arguments can be retrieved using `*command-line-args*` .
2020-01-12 19:41:53 +00:00
### Additional namespaces
2019-08-10 16:50:48 +00:00
2020-01-12 19:41:53 +00:00
#### babashka.classpath
2019-09-04 12:37:07 +00:00
2020-01-12 19:41:53 +00:00
Contains the function `add-classpath` which can be used to add to the classpath
dynamically:
``` clojure
2020-01-12 19:46:00 +00:00
(require '[babashka.classpath :refer [add-classpath]]
'[clojure.java.shell :refer [sh]])
2020-01-12 19:41:53 +00:00
(def medley-dep '{:deps {medley {:git/url "https://github.com/borkdude/medley"
:sha "91adfb5da33f8d23f75f0894da1defe567a625c0"}}})
2020-01-12 19:46:00 +00:00
(def cp (:out (sh "clojure" "-Spath" "-Sdeps" (str medley-dep))))
2020-01-12 19:41:53 +00:00
(add-classpath cp)
(require '[medley.core :as m])
(m/index-by :id [{:id 1} {:id 2}]) ;;=> {1 {:id 1}, 2 {:id 2}}
```
#### babashka.wait
Contains the functions: `wait-for-port` and `wait-for-path` .
Usage of `wait-for-port` :
2019-09-04 12:37:07 +00:00
``` clojure
2019-10-14 09:24:37 +00:00
(wait/wait-for-port "localhost" 8080)
(wait/wait-for-port "localhost" 8080 {:timeout 1000 :pause 1000})
2019-09-04 12:37:07 +00:00
```
2019-10-14 09:24:37 +00:00
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.
2020-01-12 19:41:53 +00:00
Usage of `wait-for-path` :
2019-10-14 09:24:37 +00:00
``` clojure
(wait/wait-for-path "/tmp/wait-path-test")
(wait/wait-for-path "/tmp/wait-path-test" {:timeout 1000 :pause 1000})
```
2020-01-12 19:41:53 +00:00
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.
2020-01-12 19:50:09 +00:00
The namespace `babashka.wait` is aliased as `wait` in the `user` namespace.
2019-09-04 12:37:07 +00:00
2020-01-12 19:41:53 +00:00
#### babashka.signal
Contains the function `signal/pipe-signal-received?` . Usage:
2019-09-07 08:43:53 +00:00
``` clojure
2020-01-12 19:18:37 +00:00
(signal/pipe-signal-received?)
2019-09-07 08:43:53 +00:00
```
Returns true if `PIPE` signal was received. Example:
``` shellsession
2020-02-06 22:03:32 +00:00
$ bb '((fn [x] (println x) (when (not (signal/pipe-signal-received?)) (recur (inc x)))) 0)' | head -n2
2019-09-07 08:43:53 +00:00
1
2
```
2020-01-12 19:50:09 +00:00
The namespace `babashka.signal` is aliased as `signal` in the `user` namespace.
2020-01-12 19:41:53 +00:00
2019-08-14 11:38:39 +00:00
## Running a file
2019-08-15 10:55:58 +00:00
Scripts may be executed from a file using `-f` or `--file` :
2019-08-14 11:38:39 +00:00
``` shellsession
2019-08-15 07:08:30 +00:00
bb -f download_html.clj
2019-08-14 11:38:39 +00:00
```
2019-08-18 05:34:47 +00:00
Files can also be loaded inline using `load-file` :
``` shellsession
bb '(load-file "script.clj")'
```
2019-08-14 11:38:39 +00:00
Using `bb` with a shebang also works:
2019-08-15 07:04:14 +00:00
``` clojure
2019-09-07 08:49:31 +00:00
#!/usr/bin/env bb
2019-08-15 04:28:00 +00:00
(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]
2019-08-15 07:08:30 +00:00
(println "Writing file:" file)
2019-08-15 04:28:00 +00:00
(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)
2019-08-14 11:38:39 +00:00
```
2019-08-15 08:41:37 +00:00
``` shellsession
$ ./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
```
2019-09-07 08:49:31 +00:00
If `/usr/bin/env` doesn't work for you, you can use the following workaround:
``` shellsession
$ cat script.clj
#!/bin/sh
#_(
"exec" "bb" "$0" hello "$@"
)
(prn *command-line-args* )
./script.clj 1 2 3
("hello" "1" "2" "3")
```
2019-12-12 22:07:35 +00:00
## Preloads
The environment variable `BABASHKA_PRELOADS` allows to define code that will be
available in all subsequent usages of babashka.
``` shellsession
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:
``` shellsession
2019-12-20 22:51:24 +00:00
$ bb '(-> (foo *input* ) bar)' << < 1
2019-12-12 22:07:35 +00:00
6
```
You can also preload an entire file using `load-file` :
``` shellsession
export BABASHKA_PRELOADS='(load-file "my_awesome_prelude.clj")'
```
2019-12-20 22:51:24 +00:00
Note that `*input*` is not available in preloads.
2019-12-12 22:07:35 +00:00
## Classpath
Babashka accepts a `--classpath` option that will be used to search for
namespaces and load them:
``` clojure
$ 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!
```
Note that you can use the `clojure` tool to produce classpaths and download dependencies:
``` shellsession
$ cat deps.edn
{:deps
2019-12-28 21:52:56 +00:00
{my_gist_script
{:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
:sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}
:aliases {:my-script {:main-opts ["-m" "my-gist-script"]}}}
2019-12-12 22:07:35 +00:00
$ CLASSPATH=$(clojure -Spath)
$ bb --classpath "$CLASSPATH" --main my-gist-script
Hello from gist script!
```
If there is no `--classpath` argument, the `BABASHKA_CLASSPATH` environment
2019-12-13 17:42:38 +00:00
variable will be used:
2019-12-12 22:07:35 +00:00
``` shellsession
$ export BABASHKA_CLASSPATH=$(clojure -Spath)
$ export BABASHKA_PRELOADS="(require '[my-gist-script])"
$ bb "(my-gist-script/-main)"
Hello from gist script!
```
2020-01-02 13:31:13 +00:00
### Deps.clj
2020-01-02 13:34:26 +00:00
The [`deps.clj` ](https://github.com/borkdude/deps.clj/ ) script can be used to work with `deps.edn` -based projects:
2019-12-28 21:52:56 +00:00
``` shell
2019-12-28 22:06:48 +00:00
$ deps.clj -A:my-script -Scommand "bb -cp {{classpath}} {{main-opts}}"
2019-12-28 21:52:56 +00:00
Hello from gist script!
```
2020-01-02 13:41:59 +00:00
Create these aliases for brevity:
2020-01-02 13:31:13 +00:00
``` shell
2020-01-02 13:41:59 +00:00
$ alias bbk='deps.clj -Scommand "bb -cp {{classpath}} {{main-opts}}"'
2020-01-02 13:45:41 +00:00
$ alias babashka='rlwrap deps.clj -Scommand "bb -cp {{classpath}} {{main-opts}}"'
2020-01-02 13:41:59 +00:00
$ bbk -A:my-script
2020-01-02 13:31:13 +00:00
Hello from gist script!
2020-01-02 13:41:59 +00:00
$ 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
2020-01-02 13:31:13 +00:00
```
2019-12-28 21:52:56 +00:00
## Uberscript
2019-12-28 21:55:01 +00:00
The `--uberscript` option collects the expressions in
2019-12-28 21:52:56 +00:00
`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
2019-12-28 22:50:05 +00:00
convenient for debugging and deployment.
2019-12-28 21:52:56 +00:00
Given the `deps.edn` from above:
2019-12-28 22:04:38 +00:00
``` clojure
2019-12-28 22:06:48 +00:00
$ deps.clj -A:my-script -Scommand "bb -cp {{classpath}} {{main-opts}} --uberscript my-script.clj"
2019-12-28 21:52:56 +00:00
$ cat my-script.clj
(ns my-gist-script)
(defn -main [& args]
2019-12-28 22:11:25 +00:00
(println "Hello from gist script!"))
2019-12-28 23:15:07 +00:00
(ns user (:require [my-gist-script]))
(apply my-gist-script/-main *command-line-args* )
2019-12-28 21:52:56 +00:00
$ bb my-script.clj
Hello from gist script!
```
2019-09-11 21:37:25 +00:00
## Parsing command line arguments
Babashka ships with `clojure.tools.cli` :
``` clojure
(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))
```
``` shellsession
$ bb script.clj
{:port 80}
$ bb script.clj -h
{:port 80, :help true}
```
2019-10-26 21:53:10 +00:00
## Reader conditionals
2020-02-19 22:58:49 +00:00
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.
2019-10-26 21:53:10 +00:00
``` clojure
2020-02-19 22:58:49 +00:00
$ bb "#?(:bb :hello :clj :bye)"
:hello
$ bb "#?(:clj :bye :bb :hello)"
:bye
2019-10-26 21:53:10 +00:00
2020-02-19 22:58:49 +00:00
$ bb "[1 2 #?@(:bb [] :clj [1])]"
[1 2]
2019-10-26 21:53:10 +00:00
```
2020-01-30 10:08:12 +00:00
## Running tests
Babashka bundles `clojure.test` . To make CI scripts fail you can use a simple
runner like this:
``` shell
#!/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)))"
```
2020-02-07 08:33:02 +00:00
## REPL
2019-08-31 18:17:36 +00:00
2020-02-07 08:33:02 +00:00
Babashka supports both a REPL and socket REPL. To start the REPL, type:
``` shell
$ bb --repl
```
To get history with up and down arrows, use `rlwrap` :
``` shell
$ rlwrap bb --repl
```
To start the socket REPL you can do this:
2019-08-31 18:17:36 +00:00
``` shellsession
$ bb --socket-repl 1666
Babashka socket REPL started at localhost:1666
```
Now you can connect with your favorite socket REPL client:
``` shellsession
$ rlwrap nc 127.0.0.1 1666
2019-08-31 19:22:31 +00:00
Babashka v0.0.14 REPL.
2019-08-31 18:17:36 +00:00
Use :repl/quit or :repl/exit to quit the REPL.
Clojure rocks, Bash reaches.
bb=> (+ 1 2 3)
6
bb=> :repl/quit
$
```
A socket REPL client for Emacs is
[inf-clojure ](https://github.com/clojure-emacs/inf-clojure ).
2019-09-04 12:37:07 +00:00
## Spawning and killing a process
2019-12-17 10:27:40 +00:00
Use the `java.lang.ProcessBuilder` class.
2019-09-04 12:37:07 +00:00
Example:
``` clojure
2019-12-17 10:27:40 +00:00
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
2019-09-04 12:37:07 +00:00
```
2019-12-17 10:27:40 +00:00
Also see this [example ](examples/process_builder.clj ).
2019-09-07 08:43:53 +00:00
## Async
2020-03-01 21:45:55 +00:00
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:
2019-09-07 08:43:53 +00:00
``` clojure
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
```
2020-03-01 15:56:28 +00:00
Note: the `go` macro is available for compatibility with JVM programs, but the
implementation maps to `clojure.core.async/thread` and the single exclamation
2020-03-01 16:43:10 +00:00
mark operations (`< !`, `>!` , etc.) map to the double exclamation mark operations
2020-03-01 21:45:55 +00:00
(`< !!`, `>!!` , etc.). It will not "park" threads, like on the JVM.
2020-03-01 15:56:28 +00:00
2020-02-29 08:22:22 +00:00
## HTTP
For making HTTP requests you can use:
- `slurp` for simple `GET` requests
- [clj-http-lite ](https://github.com/borkdude/clj-http-lite ) as a library
2020-03-02 12:00:50 +00:00
- `curl` via `clojure.java.shell` . Also see
[babashka.curl ](https://github.com/borkdude/babashka.curl ).
2020-02-29 08:22:22 +00:00
### HTTP over Unix sockets
This can be useful for talking to Docker:
``` clojure
(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"]
```
2019-10-07 21:11:49 +00:00
## Differences with Clojure
2019-10-07 21:09:35 +00:00
Babashka is implemented using the [Small Clojure
Interpreter](https://github.com/borkdude/sci). This means that a snippet or
script is not compiled to JVM bytecode, but executed form by form by a runtime
2020-03-02 08:50:47 +00:00
which implements a sufficiently large subset of Clojure. Babashka is compiled to
a native binary using [GraalVM ](https://github.com/oracle/graal ). 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
2019-10-07 21:09:35 +00:00
same. Multi-threading is supported (`pmap`, `future` ).
2019-10-07 21:11:49 +00:00
Differences with Clojure:
2019-10-07 21:09:35 +00:00
2020-03-02 08:50:47 +00:00
- A pre-selected set of Java classes are supported. You cannot add Java classes
at runtime.
2019-10-07 21:09:35 +00:00
- Interpretation comes with overhead. Therefore tight loops are likely slower
2020-03-02 08:50:47 +00:00
than in Clojure on the JVM. In general interpretation yields slower programs
than compiled programs.
2019-10-07 21:09:35 +00:00
- No support for unboxed types.
2019-12-20 09:46:31 +00:00
## External resources
2019-12-25 13:49:29 +00:00
### Tools and libraries
2019-12-24 10:31:31 +00:00
The following libraries are known to work with Babashka:
2019-12-25 13:49:29 +00:00
#### [deps.clj](https://github.com/borkdude/deps.clj)
A port of the [clojure ](https://github.com/clojure/brew-install/ ) bash script to
2019-12-29 13:56:16 +00:00
Clojure / babashka.
2020-01-09 13:58:03 +00:00
#### [spartan.spec](https://github.com/borkdude/spartan.spec/)
An babashka-compatible implementation of `clojure.spec.alpha` .
2020-01-29 22:53:01 +00:00
#### [missing.test.assertions](https://github.com/borkdude/missing.test.assertions)
This library checks if no assertions have been made in a test:
``` shell
$ 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}
```
2020-02-20 09:56:07 +00:00
#### [medley](https://github.com/weavejester/medley/)
2020-01-02 12:42:59 +00:00
2020-02-20 09:56:07 +00:00
Requires `bb` >= v0.0.71. Latest coordinates checked with with bb:
``` clojure
{:git/url "https://github.com/weavejester" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}
```
Example:
``` shell
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {medley {:git/url "https://github.com/weavejester" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}}}')
$ bb -e "(require '[medley.core :as m]) (m/index-by :id [{:id 1} {:id 2}])"
{1 {:id 1}, 2 {:id 2}}
```
2020-01-02 12:42:59 +00:00
2019-12-24 10:31:31 +00:00
#### [clj-http-lite](https://github.com/borkdude/clj-http-lite)
2019-12-25 13:49:29 +00:00
This fork does not depend on any other libraries. Example:
2019-12-24 10:31:31 +00:00
``` shell
2020-01-08 07:45:41 +00:00
$ export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {clj-http-lite {:git/url "https://github.com/borkdude/clj-http-lite" :sha "f44ebe45446f0f44f2b73761d102af3da6d0a13e"}}}' -Spath)"
2019-12-24 10:31:31 +00:00
$ bb "(require '[clj-http.lite.client :as client]) (:status (client/get \"https://www.clojure.org\"))"
200
```
#### [limit-break](https://github.com/technomancy/limit-break)
2020-02-20 09:56:07 +00:00
A debug REPL library.
Latest coordinates checked with with bb:
``` clojure
{:git/url "https://github.com/technomancy/limit-break" :sha "050fcfa0ea29fe3340927533a6fa6fffe23bfc2f" :deps/manifest :deps}
```
Example:
2019-12-24 10:31:31 +00:00
``` shell
$ 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
```
2020-01-21 14:53:04 +00:00
#### [clojure-csv](https://github.com/davidsantiago/clojure-csv)
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:
``` shell
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\"))
"
```
2020-02-20 10:06:03 +00:00
#### [regal](https://github.com/lambdaisland/regal)
2020-02-17 11:09:34 +00:00
2020-02-20 09:56:07 +00:00
Requires `bb` >= v0.0.71. Latest coordinates checked with with bb:
``` clojure
{:git/url "https://github.com/lambdaisland/regal" :sha "8d300f8e15f43480801766b7762530b6d412c1e6"}
```
Example:
``` shell
$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {regal {:git/url "https://github.com/lambdaisland/regal" :sha "8d300f8e15f43480801766b7762530b6d412c1e6"}}}')
$ bb -e "(require '[lambdaisland.regal :as regal]) (regal/regex [:* \"ab\"])"
#"(?:\Qab\E)*"
```
2020-02-17 11:09:34 +00:00
2020-01-29 22:53:01 +00:00
#### [spartan.test](https://github.com/borkdude/spartan.test/)
A minimal test framework compatible with babashka. This library is deprecated
since babashka v0.0.68 which has `clojure.test` built-in.
2019-12-20 09:46:31 +00:00
### Blogs
2019-12-20 09:45:07 +00:00
2020-02-21 11:15:47 +00:00
- [Babashka: a quick example ](https://juxt.pro/blog/posts/babashka.html ) by Malcolm Sparks
2019-12-21 20:05:40 +00:00
- [Clojure Start Time in 2019 ](https://stuartsierra.com/2019/12/21/clojure-start-time-in-2019 ) by Stuart Sierra
2019-12-20 09:45:07 +00:00
- [Advent of Random
Hacks](https://lambdaisland.com/blog/2019-12-19-advent-of-parens-19-advent-of-random-hacks)
by Arne Brasseur
- [Clojure in the Shell ](https://lambdaisland.com/blog/2019-12-05-advent-of-parens-5-clojure-in-the-shell ) by Arne Brasseur
- [Clojure Tool ](https://purelyfunctional.tv/issues/purelyfunctional-tv-newsletter-351-clojure-tool-babashka/ ) by Eric Normand
2020-01-08 20:30:50 +00:00
## [Developing Babashka](doc/dev.md)
2019-08-09 12:51:42 +00:00
2019-08-13 07:20:48 +00:00
## Related projects
- [planck ](https://planck-repl.org/ )
- [joker ](https://github.com/candid82/joker )
- [closh ](https://github.com/dundalek/closh )
- [lumo ](https://github.com/anmonteiro/lumo )
2019-08-10 16:50:48 +00:00
## Gallery
Here's a gallery of more useful examples. Do you have a useful example? PR
welcome!
2019-11-08 19:23:43 +00:00
### Delete a list of files returned by a Unix command
```
2019-12-20 22:51:24 +00:00
find . | grep conflict | bb -i '(doseq [f *input* ] (.delete (io/file f)))'
2019-11-08 19:23:43 +00:00
```
2019-11-27 21:55:21 +00:00
### Calculate aggregate size of directory
``` clojure
#!/usr/bin/env bb
(as-> (io/file (or (first *command-line-args* ) ".")) $
(file-seq $)
(map #(.length %) $)
(reduce + $)
(/ $ (* 1024 1024))
(println (str (int $) "M")))
```
``` shellsession
$ dir-size
130M
$ dir-size ~/Dropbox/bin
233M
```
2019-08-10 20:52:30 +00:00
### Shuffle the lines of a file
``` shellsession
$ cat /tmp/test.txt
1 Hello
2 Clojure
2019-08-13 11:43:15 +00:00
3 Babashka
2019-08-10 20:52:30 +00:00
4 Goodbye
2019-12-20 22:51:24 +00:00
$ < /tmp/test.txt bb -io '(shuffle *input* )'
2019-08-13 11:43:15 +00:00
3 Babashka
2019-08-10 20:52:30 +00:00
2 Clojure
4 Goodbye
1 Hello
```
2019-08-10 16:50:48 +00:00
### Fetch latest Github release tag
2020-03-01 11:06:19 +00:00
``` shell
(require '[clojure.java.shell :refer [sh]]
'[cheshire.core :as json])
2019-08-10 16:50:48 +00:00
2020-03-01 11:06:19 +00:00
(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"
2019-08-10 18:34:24 +00:00
```
2020-02-02 12:14:29 +00:00
### Generate deps.edn entry for a gitlib
2019-08-10 18:34:24 +00:00
2020-02-02 12:14:29 +00:00
``` clojure
#!/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})
```
``` shell
$ gitlib.clj nate fs
{:git/url "https://github.com/nate/fs", :sha "75b9fcd399ac37cb4f9752a4c7a6755f3fbbc000"}
2020-02-02 12:33:26 +00:00
$ clj -Sdeps "{:deps {fs $(gitlib.clj nate fs)}}" \
-e "(require '[nate.fs :as fs]) (fs/creation-time \".\")"
2020-02-02 12:29:50 +00:00
#object[java.nio.file.attribute.FileTime 0x5c748168 "2019-07-05T14:06:26Z"]
2019-08-10 16:50:48 +00:00
```
2019-11-03 20:38:06 +00:00
### View download statistics from Clojars
2019-12-18 07:53:32 +00:00
Contributed by [@plexus ](https://github.com/plexus ).
2019-11-03 20:38:06 +00:00
``` shellsession
2019-11-03 21:43:38 +00:00
$ curl https://clojars.org/stats/all.edn |
2019-12-20 22:51:24 +00:00
bb -o '(for [[[group art] counts] *input* ] (str (reduce + (vals counts)) " " group "/" art))' |
2019-11-03 20:39:36 +00:00
sort -rn |
less
2019-11-03 21:43:01 +00:00
14113842 clojure-complete/clojure-complete
9065525 clj-time/clj-time
8504122 cheshire/cheshire
...
2019-11-03 20:38:06 +00:00
```
2019-11-27 17:08:03 +00:00
### Portable tree command
2019-12-13 19:59:09 +00:00
See [examples/tree.clj ](https://github.com/borkdude/babashka/blob/master/examples/tree.clj ).
2019-11-27 17:20:50 +00:00
2019-11-27 17:08:03 +00:00
``` shellsession
$ 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
2019-12-01 12:50:32 +00:00
...
2019-11-27 17:08:03 +00:00
```
2019-12-13 19:59:09 +00:00
### List outdated maven dependencies
See [examples/outdated.clj ](https://github.com/borkdude/babashka/blob/master/examples/outdated.clj ).
2019-12-13 20:00:20 +00:00
Inspired by an idea from [@seancorfield ](https://github.com/seancorfield ).
2019-12-13 19:59:09 +00:00
``` shellsession
$ 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
```
2019-12-17 14:35:03 +00:00
### Convert project.clj to deps.edn
2019-12-18 07:52:14 +00:00
Contributed by [@plexus ](https://github.com/plexus ).
2019-12-18 07:51:40 +00:00
2019-12-17 14:37:05 +00:00
``` shellsession
$ cat project.clj |
sed -e 's/#=//g' -e 's/~@//g' -e 's/~//g' |
2019-12-20 22:51:24 +00:00
bb '(let [{:keys [dependencies source-paths resource-paths]} (apply hash-map (drop 3 *input* ))]
2019-12-17 14:37:05 +00:00
{:paths (into source-paths resource-paths)
:deps (into {} (for [[d v] dependencies] [d {:mvn/version v}]))}) ' |
jet --pretty > deps.edn
2019-12-17 14:35:03 +00:00
```
2019-12-18 15:38:21 +00:00
### Print current time in California
See [examples/pst.clj ](https://github.com/borkdude/babashka/blob/master/examples/pst.clj )
2020-01-06 12:22:43 +00:00
### Tiny http server
See [examples/http_server.clj ](https://github.com/borkdude/babashka/blob/master/examples/http_server.clj )
Original by [@souenzzo ](https://gist.github.com/souenzzo/a959a4c5b8c0c90df76fe33bb7dfe201 )
2020-02-20 11:20:55 +00:00
### Print random docstring
2020-02-20 11:45:44 +00:00
See [examples/random_doc.clj ](https://github.com/borkdude/babashka/blob/master/examples/random_doc.clj )
2020-02-20 11:20:55 +00:00
``` shell
2020-02-20 11:45:44 +00:00
$ examples/random_doc.clj
2020-02-20 11:20:55 +00:00
-------------------------
2020-02-20 11:45:44 +00:00
clojure.core/ffirst
2020-02-20 11:20:55 +00:00
([x])
2020-02-20 11:45:44 +00:00
Same as (first (first x))
2020-02-20 11:20:55 +00:00
```
2019-11-03 20:28:16 +00:00
## Thanks
2019-11-03 20:33:01 +00:00
- [adgoji ](https://www.adgoji.com/ ) for financial support
2020-02-23 22:20:16 +00:00
- [Nikita Prokopov ](https://github.com/tonsky ) for the logo
- [contributors ](https://github.com/borkdude/babashka/graphs/contributors ) and
other users posting issues with bug reports and ideas
2019-11-03 20:28:16 +00:00
2019-08-09 12:51:42 +00:00
## License
Copyright © 2019 Michiel Borkent
2019-09-04 12:37:07 +00:00
Distributed under the EPL License. See LICENSE.
This project contains code from:
- Clojure, which is licensed under the same EPL License.