2019-08-13 11:43:15 +00:00
|
|
|
# babashka
|
2019-08-09 12:51:42 +00:00
|
|
|
|
|
|
|
|
[](https://circleci.com/gh/borkdude/babashka/tree/master)
|
|
|
|
|
[](https://clojars.org/borkdude/babashka)
|
|
|
|
|
[](https://cljdoc.org/d/borkdude/babashka/CURRENT)
|
|
|
|
|
|
2019-08-12 12:42:45 +00:00
|
|
|
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
|
2019-08-15 09:18:25 +00:00
|
|
|
can use a bit of Clojure in their shell scripts, it may be useful.
|
2019-08-09 12:51:42 +00:00
|
|
|
|
2019-08-17 22:31:39 +00:00
|
|
|
Babashka runs as a binary which results in faster startup times:
|
2019-08-15 09:16:15 +00:00
|
|
|
|
2019-08-15 09:05:59 +00:00
|
|
|
``` shellsession
|
2019-08-17 22:31:39 +00:00
|
|
|
$ time clojure -e "(+ 1 2 3)"
|
|
|
|
|
6
|
|
|
|
|
clojure -e "(+ 1 2 3)" 3.29s user 0.32s system 99% cpu 3.638 total
|
2019-08-15 09:05:59 +00:00
|
|
|
|
2019-08-17 22:40:01 +00:00
|
|
|
$ time planck -e '(+ 1 2 3)'
|
2019-08-17 22:31:39 +00:00
|
|
|
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
|
2019-08-15 09:05:59 +00:00
|
|
|
```
|
|
|
|
|
|
2019-08-17 22:31:39 +00:00
|
|
|
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.
|
2019-08-15 09:16:15 +00:00
|
|
|
|
2019-08-09 12:51:42 +00:00
|
|
|
## Status
|
|
|
|
|
|
2019-08-10 16:50:48 +00:00
|
|
|
Experimental. Breaking changes are expected to happen at this phase.
|
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-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
|
|
|
|
|
|
2019-08-09 14:02:08 +00:00
|
|
|
You may also download a binary from [Github](https://github.com/borkdude/babashka/releases).
|
|
|
|
|
|
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-08-16 20:22:58 +00:00
|
|
|
bb [ --help ] | [ --version ] | ( [ -i ] [ -o ] | [ -io ] ) [ --stream ] ( expression | -f <file> )
|
2019-08-09 17:58:11 +00:00
|
|
|
```
|
|
|
|
|
|
2019-08-14 11:38:39 +00:00
|
|
|
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`
|
2019-08-14 16:18:23 +00:00
|
|
|
- `clojure.edn` aliased as `edn` (only `read-string` is available)
|
2019-08-14 11:38:39 +00:00
|
|
|
- `clojure.java.shell` aliases as `shell` (only `sh` is available)
|
|
|
|
|
|
|
|
|
|
From Java the following is available:
|
2019-08-13 22:19:15 +00:00
|
|
|
|
2019-08-17 20:19:46 +00:00
|
|
|
- `System`: `exit`, `getProperty`, `setProperty`, `getProperties`, `getenv`
|
2019-08-15 04:28:00 +00:00
|
|
|
|
|
|
|
|
Special vars:
|
|
|
|
|
|
2019-08-16 20:22:58 +00:00
|
|
|
- `*in*`: contains the input read from stdin (EDN by default, multiple lines with the `-i` option)
|
|
|
|
|
<!-- - `bb/*in*`: the unprocessed input from stdin -->
|
2019-08-15 04:28:00 +00:00
|
|
|
- `*command-line-args*`: contain the command line args
|
2019-08-10 16:50:48 +00:00
|
|
|
|
2019-08-09 12:51:42 +00:00
|
|
|
Examples:
|
|
|
|
|
|
|
|
|
|
``` shellsession
|
2019-08-10 16:50:48 +00:00
|
|
|
$ ls | bb -i '*in*'
|
2019-08-09 15:38:26 +00:00
|
|
|
["LICENSE" "README.md" "bb" "doc" "pom.xml" "project.clj" "reflection.json" "resources" "script" "src" "target" "test"]
|
|
|
|
|
|
2019-08-10 16:50:48 +00:00
|
|
|
$ ls | bb -i '(count *in*)'
|
|
|
|
|
12
|
2019-08-09 15:38:26 +00:00
|
|
|
|
2019-08-10 07:52:21 +00:00
|
|
|
$ bb '(vec (dedupe *in*))' <<< '[1 1 1 1 2]'
|
2019-08-09 12:51:42 +00:00
|
|
|
[1 2]
|
|
|
|
|
|
2019-08-10 16:50:48 +00:00
|
|
|
$ bb '(filterv :foo *in*)' <<< '[{:foo 1} {:bar 2}]'
|
|
|
|
|
[{:foo 1}]
|
2019-08-09 12:51:42 +00:00
|
|
|
```
|
|
|
|
|
|
2019-08-09 15:38:26 +00:00
|
|
|
``` shellsession
|
2019-08-10 16:50:48 +00:00
|
|
|
$ bb '(#(+ %1 %2 %3) 1 2 *in*)' <<< 3
|
2019-08-09 15:41:33 +00:00
|
|
|
6
|
2019-08-09 16:17:28 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
``` shellsession
|
2019-08-10 16:50:48 +00:00
|
|
|
$ ls | bb -i '(filterv #(re-find #"reflection" %) *in*)'
|
2019-08-09 15:38:26 +00:00
|
|
|
["reflection.json"]
|
|
|
|
|
```
|
|
|
|
|
|
2019-08-13 22:19:15 +00:00
|
|
|
``` shellsession
|
2019-08-14 11:38:39 +00:00
|
|
|
$ bb '(run! #(shell/sh "touch" (str "/tmp/test/" %)) (range 100))'
|
2019-08-13 22:19:15 +00:00
|
|
|
$ ls /tmp/test | bb -i '*in*'
|
|
|
|
|
["0" "1" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "2" "20" "21" ...]
|
|
|
|
|
```
|
|
|
|
|
|
2019-08-10 20:52:30 +00:00
|
|
|
More examples can be found in the [gallery](#gallery).
|
2019-08-09 21:08:49 +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
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Using `bb` with a shebang also works:
|
|
|
|
|
|
2019-08-15 07:04:14 +00:00
|
|
|
``` clojure
|
2019-08-15 07:33:11 +00:00
|
|
|
#!/usr/bin/env bb -f
|
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-08-17 15:38:24 +00:00
|
|
|
## Enabling SSL
|
|
|
|
|
|
2019-08-17 20:19:46 +00:00
|
|
|
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 `<JAVA_HOME>/jre/lib` or
|
|
|
|
|
`<JAVA_HOME>/jre/<platform>/lib`. It is also bundled with GraalVM.
|
2019-08-17 15:38:24 +00:00
|
|
|
|
2019-08-17 20:19:46 +00:00
|
|
|
Example:
|
2019-08-17 15:38:24 +00:00
|
|
|
|
|
|
|
|
``` shellsession
|
2019-08-17 20:19:46 +00:00
|
|
|
$ cat /tmp/https_get.clj
|
|
|
|
|
#!/usr/bin/env bb -f
|
2019-08-17 15:38:24 +00:00
|
|
|
|
2019-08-17 20:19:46 +00:00
|
|
|
(System/setProperty
|
|
|
|
|
"java.library.path"
|
|
|
|
|
"/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/jre/lib")
|
2019-08-17 15:38:24 +00:00
|
|
|
|
2019-08-17 20:19:46 +00:00
|
|
|
(slurp (first *command-line-args*))
|
2019-08-17 15:38:24 +00:00
|
|
|
```
|
|
|
|
|
|
2019-08-17 20:19:46 +00:00
|
|
|
``` shellsession
|
|
|
|
|
$ /tmp/https_get.clj https://www.google.com | bb '(subs *in* 0 50)'
|
|
|
|
|
"<!doctype html><html itemscope=\"\" itemtype=\"http:/"
|
|
|
|
|
```
|
2019-08-17 15:38:24 +00:00
|
|
|
|
2019-08-09 12:51:42 +00:00
|
|
|
## Test
|
|
|
|
|
|
2019-08-09 18:01:12 +00:00
|
|
|
Test on the JVM:
|
2019-08-09 12:51:42 +00:00
|
|
|
|
|
|
|
|
script/test
|
|
|
|
|
|
2019-08-09 18:01:12 +00:00
|
|
|
Although this tool doesn't offer any benefit when running on the JVM, it is
|
|
|
|
|
convenient for development.
|
|
|
|
|
|
2019-08-09 12:51:42 +00:00
|
|
|
Test the native version:
|
|
|
|
|
|
|
|
|
|
BABASHKA_TEST_ENV=native script/test
|
|
|
|
|
|
|
|
|
|
## Build
|
|
|
|
|
|
|
|
|
|
You will need leiningen and GraalVM.
|
|
|
|
|
|
2019-08-17 20:27:13 +00:00
|
|
|
This repo contains a submodule, so you will have clone that too. If you're
|
|
|
|
|
doing that for the first time:
|
|
|
|
|
|
|
|
|
|
``` shellsession
|
|
|
|
|
$ git submodule update --init --recursive
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
and for subsequent updates:
|
|
|
|
|
|
|
|
|
|
``` shellsession
|
|
|
|
|
$ git submodule update --recursive
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
To build this project, set `$GRAALVM_HOME` to the GraalVM distribution directory.
|
|
|
|
|
|
|
|
|
|
Then run:
|
|
|
|
|
|
2019-08-09 12:51:42 +00:00
|
|
|
script/compile
|
|
|
|
|
|
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-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
|
|
|
|
|
|
|
|
|
|
$ < /tmp/test.txt bb -io '(shuffle *in*)'
|
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
|
|
|
|
|
|
|
|
|
|
For converting JSON to EDN, see [jet](https://github.com/borkdude/jet).
|
|
|
|
|
|
|
|
|
|
``` shellsession
|
2019-08-11 07:05:25 +00:00
|
|
|
$ curl -s https://api.github.com/repos/borkdude/babashka/tags |
|
2019-08-12 07:28:37 +00:00
|
|
|
jet --from json --keywordize --to edn |
|
|
|
|
|
bb '(-> *in* first :name (subs 1))'
|
2019-08-10 18:34:24 +00:00
|
|
|
"0.0.4"
|
|
|
|
|
```
|
|
|
|
|
|
2019-08-10 18:52:13 +00:00
|
|
|
### Get latest OS-specific download url from Github
|
2019-08-10 18:34:24 +00:00
|
|
|
|
|
|
|
|
``` shellsession
|
2019-08-11 07:05:25 +00:00
|
|
|
$ curl -s https://api.github.com/repos/borkdude/babashka/releases |
|
2019-08-12 07:28:37 +00:00
|
|
|
jet --from json --keywordize |
|
|
|
|
|
bb '(-> *in* first :assets)' |
|
|
|
|
|
bb '(some #(re-find #".*linux.*" (:browser_download_url %)) *in*)'
|
2019-08-10 18:52:13 +00:00
|
|
|
"https://github.com/borkdude/babashka/releases/download/v0.0.4/babashka-0.0.4-linux-amd64.zip"
|
2019-08-10 16:50:48 +00:00
|
|
|
```
|
|
|
|
|
|
2019-08-15 09:43:59 +00:00
|
|
|
## Support this project
|
|
|
|
|
|
|
|
|
|
Do you enjoy this project? Consider buying me a [hot
|
|
|
|
|
beverage](https://ko-fi.com/borkdude).
|
|
|
|
|
|
2019-08-09 12:51:42 +00:00
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
Copyright © 2019 Michiel Borkent
|
|
|
|
|
|
|
|
|
|
Distributed under the EPL License, same as Clojure. See LICENSE.
|