Command mode uses simple shell-like syntax
git commit -a
Forms starting with parenthesis are considered as Clojure code
(+ 1 2)
Glob patterns and environment variables are expanded
ls *.txt
ls $HOME
cd ~/Downloads
Use output from a command or function as arguments to other command
echo (+ 1 2)
echo (sh-str date)
Piping output between commands
ls | head
Piping with functions works similarly to ->>
threading macro
ls | (clojure.string/upper-case)
ls | #(clojure.string/replace % #"\.txt" ".md")
Use special |>
pipe operator to split input into sequence of lines
ls |> (reverse) | (take 5)
Redirects - note that there must be spaces around redirection operators >
and >>
ls > files.txt
echo hi >> file.txt
ls 2 > files.txt
Command status
echo hi && echo OK
! echo hi || echo FAILED
Most of helper utilities can be replaced with functions on sequences.
bash: ls | head -n 5
closh: ls |> (take 5)
bash: ls | tail -n 5
closh: ls |> (take-last 5)
bash: ls | tail -n +5
closh: ls |> (drop 4)
; Print filenames starting with "."
bash: ls -a | grep "^\\."
closh: ls -a |> (filter #(re-find #"^\." %))
; Print only odd numbered lines counting from 1
bash: ls | sed -n 1~2p
closh: ls |> (keep-indexed #(when (odd? (inc %1)) %2))
; Math
bash: echo '(1 + sqrt(5))/2' | bc -l
closh: (/ (+ 1 (Math.sqrt 5)) 2)
For loops:
for f in /sys/bus/usb/devices/*/power/wakeup; do echo $f; cat $f; done
; Using doseq
(doseq [f (expand "/sys/bus/usb/devices/*/power/wakeup")] (println f) (sh cat (str f)))
; Or multi pipes
ls /sys/bus/usb/devices/*/power/wakeup |> (map #(str % "\n" (sh-str cat (str %)))) | cat
If conditionals:
if test -f package.json; then echo file exists; else echo no file; fi
echo (if (sh-ok test -f package.json) "file exists" "no file")
bash: ls; echo hi
closh: (sh ls) (sh echo hi)
History gets saved to the file ~/.closh/closh.sqlite
which is a SQLite database.
Use up and down arrows to cycle through history. First history from a current session is used, then history from all other sessions is used.
If you type some text and press up then the text will be used to match beginning of the command (prefix mode). Pressing ctrl-r will switch to matching anywhere in the command (substring mode). The search is case insensitive.
While in the history search mode you can use following controls:
- enter to accept the command and execute it
- tab to accept the command but have ability to edit it
- esc cancel search keeping the initial text
- ctrl-c cancel search and resetting the initial text
To show history you can run:
sqlite3 ~/.closh/closh.sqlite "SELECT command FROM history ORDER BY id ASC"
For convenience you can add the following to your ~/.closhrc
file:
(defcmd history []
(sh sqlite3 (str (getenv "HOME") "/.closh/closh.sqlite") "SELECT command FROM history ORDER BY id ASC" | cat))
There are some helper functions for doing common things with environment variables
setenv: set environment variable
setenv "ONE" "1"
=> ("1")
(setenv "ONE" "1")
=> ("1")
(setenv "ONE" "1" "TWO" "2")
=> ("1" "2")
getenv: get environment variable
getenv "ONE"
=> "1"
(getenv "ONE")
=> "1"
(getenv "ONE" "TWO")
=> {"ONE" "1", "TWO" "2"}
getenv
=> ;; returns a map of all environment variables
source-shell: run bash scripts and import the resulting environment variables into the closh environment
(source-shell "export ONE=42")
=> nil
getenv "ONE"
=> "42"
source-shell
defaults to bash
but you can use other shells:
(source-shell "zsh" "source complicated_script.zsh")
=> nil
getenv "SET_BY_COMPLICATED_SCRIPT"
=> "..."
To set a temporary variable while running a command, do this:
env VAR=1 command
Which is equivalent to bash:
VAR=1 command
PWD
- a path to the current working directory
The prompt can be customized by defining closh-prompt
function in ~/.closhrc
file.
For example you can use powerline prompt like this:
(require-macros '[closh.zero.core :refer [sh-str]])
(defn closh-prompt []
(sh-str powerline-shell --shell bare))
Or you can reuse existing prompt from fish shell:
(require-macros '[closh.zero.core :refer [sh-str]])
(defn closh-prompt []
(sh-str fish -c fish_prompt))
Bash prompt format can be used via decode-prompt module. Install it with npm install -g decode-prompt
. Then use it like:
(require '[cljs.nodejs])
(def decode-prompt (js/require "decode-prompt"))
(def PS1 "\\[\\033[01;32m\\]\\u@\\h\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w\\[\\033[00m\\]\\$ ")
(defn closh-prompt []
(decode-prompt PS1 #js{:env js/process.env}))
Or you can implement a custom prompt in clojure.
Closh delegates completion to existing shells. When tab completion is triggered it tries to fetch completions first from fish
, then zsh
and finally bash
. One the mentioned shells needs to be installed for completion to work.
If the completion does not work you can find out the reason by cloning the repo and trying out:
./resources/completion/completion.bash "ls "
./resources/completion/completion.zsh "ls "
./resources/completion/completion.fish "ls "
You can define helper aliases, abbreviations, functions and commands in your ~/.closhrc
file.
Aliases for defining or overriding functionality. When alias is used it does not get expanded and is saved into history as is.
Example:
(defalias ls "ls --color=auto")
Abbreviations are similar to aliases but expand to underlying definition on the command line after you type space or press enter. Therefore autocomplete can work seamlessly and also full command is saved to history. Inspired by abbr in fish (more details).
(defabbr gco "git checkout")
(defabbr gaa "git add --all")
Define Clojure functions, run in command line like: (hello "World")
.
(defn hello [name]
(str "Hello " name))
Similar to functions, but can be executed like commands without using parens. Run in command line like: hello World
.
; Define commands like with defn
(defcmd hello [name]
(str "Hello " name))
; Promote existing function to a command
(defcmd upper clojure.string/upper-case)
Prevent some expansion is same like bash with double-quoted string:
echo "*"
Disable expansion completely with a single quote:
bash: echo '$HOME'
closh: echo '$HOME ; notice there is only one quote
; if the quoted string has spaces wrap in double quotes and then prepend single quote
bash: echo '$HOME $PWD'
closh: echo '"$HOME $PWD"
- SIGINT interrupts currently running command or code
- SIGQUIT is ignored