Clojure (Babashka) shell integration for Zsh, inspired by Rash and Closh.
Lines that start with ( are evaluated as Clojure expressions by Babashka.
All other lines run as normal zsh commands.
$ (+ 1 2 3)
6
$ (str "hello" " " "world")
"hello world"
$ (-> 5 (+ 3) (* 2))
16Input lines are split into a vector. Use map, filter, etc. to process each line.
$ printf 'hello\nworld' | (map upper-case %)
HELLO
WORLD
$ printf ' a \n b ' | (map (comp upper-case trim) %)
A
B
$ printf 'apple\nbanana\ncherry' | (filter #(> (count %) 5) %)
banana
cherry
$ printf 'a\nb\nc' | (count %)
3Use %% when you need the entire input as a single string.
$ printf 'hello world' | (upper-case %%)
HELLO WORLD
$ printf 'a\nb\nc' | (count %%)
5
$ printf 'hello\nworld' | (replace %% "\n" ", ")
hello, world$ echo ' aaa \n bbb \nccc' | (map (comp upper-case trim) %) | cat -n
1 AAA
2 BBB
3 CCCThe plugin overrides the ZLE accept-line widget and checks the input when Enter is pressed:
- Starts with
(: evaluate withbb -e - Otherwise: execute as a normal zsh command
For pipelines with |, each stage that starts with ( ... ) is treated as a Clojure stage.
| Variable | Description | Example |
|---|---|---|
% |
Input lines as a vector | "a\nb" → ["a" "b"] |
%% |
Raw input as a single string | "a\nb" → "a\nb" |
| Result Type | Output Format |
|---|---|
| Sequential (vector, list) | Each element on a new line |
| String | Plain text |
| Other | pr-str representation |
clojure.string is auto-loaded with :refer :all, so functions like trim, upper-case, replace, etc. can be used without namespace prefix. (Note: :refer :all is deprecated and will be removed in a future version. Use str/ prefix instead.)
Babashka startup is typically around ~20ms, so interactive lag is minimal.
Press Tab inside parentheses to get completion for Clojure functions.
$ (map upper-c<Tab>
$ (map upper-case # completes to upper-case
$ (fs/cre<Tab>
$ (fs/create- # shows create-dir, create-file, create-temp-dir, etc.Completions are available for all Babashka built-in namespaces:
| Alias | Namespace |
|---|---|
str/ |
clojure.string |
set/ |
clojure.set |
io/ |
clojure.java.io |
fs/ |
babashka.fs |
proc/ |
babashka.process |
http/ |
babashka.http-client |
json/ |
cheshire.core |
yaml/ |
clj-yaml.core |
async/ |
clojure.core.async |
csv/ |
clojure.data.csv |
xml/ |
clojure.data.xml |
transit/ |
cognitect.transit |
And many more including clojure.core, clojure.walk, clojure.zip, hiccup.core, rewrite-clj.*, taoensso.timbre, etc.
Define your own functions in a config file:
~/.config/zsh-clj-shell/init.bb (preferred)
~/.config/zsh-clj-shell/init.clj (fallback)
Example init.bb:
(defn hello [name]
(str "Hello, " name "!"))
(defn count-words [text]
(count (str/split text #"\s+")))Usage:
$ (hello "world")
Hello, world!
$ echo "one two three" | (count-words %%)
3To reload your config without restarting the shell:
zsh-clj-shell-reload-config
- zsh 5.0+
- Babashka
zinit light hatappo/zsh-clj-shellzplug "hatappo/zsh-clj-shell"antigen bundle hatappo/zsh-clj-shellAdd to ~/.config/sheldon/plugins.toml:
[plugins.zsh-clj-shell]
github = "hatappo/zsh-clj-shell"Clone the repository to oh-my-zsh custom plugins directory:
git clone https://github.com/hatappo/zsh-clj-shell.git \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-clj-shellThen add zsh-clj-shell to your plugins in ~/.zshrc:
plugins=(... zsh-clj-shell)git clone https://github.com/hatappo/zsh-clj-shell.git
cd zsh-clj-shell
./install.shAdd the following to ~/.zshrc:
source /path/to/zsh-clj-shell/zsh-clj-shell.plugin.zshPlace this near the end of ~/.zshrc so later plugins do not replace accept-line.
To disable zsh-clj-shell in the current session:
zsh-clj-shell-unload
- Any input that starts with
(is sent to Babashka. If you need zsh subshell syntax(command), use{ command }instead. - Ambiguous lines (for example, lines that contain
||) are not transformed and are passed to zsh as-is. zsh-clj-shellhooks ZLE widgets (especiallyaccept-lineandTab). Plugins that also override these widgets can interfere depending on load order.- Known interaction point:
zsh-abbr(accept-line/ space expansion). Current releases include a compatibility path, but if expansion stops working, reload your shell and verify widget bindings withprint -- "$widgets[accept-line]". - Recommended order: load abbreviation/autosuggestion plugins first, then load
zsh-clj-shellnear the end of.zshrc.
MIT.