8.2 KiB
A novel Unix shell
This project aims to explore the potentials of the Unix shell. It is a work in progress; things will change without warning. The issues list contains many things I'm working on.
Screenshot
Elvish looks like this:
Prebuilt binaries
Up-to-date binaries for 64-bit Linux and Mac OS X. Install with sudo tar vxfz elvish-*.tar.gz -C /usr/bin
. See also Building Elvish.
Getting Started
Elvish mimics bash and zsh in a lot of places. The following shows some key differences and highlights, as well as some common tasks:
-
Put your startup script in
~/.elvish/rc.elv
. There is noalias
yet, but you can achieve the goal by defining a function:fn ls { external:ls --color $@ }
The
external:
prefix ensures that the external command namedls
will be called. Otherwise this definition will result in infinite recursion. -
The left and right prompts and be customized by modifying
le:prompt
andle:rprompt
. They can be assigned either to a function, in which their outputs are used, or a constant string. The following simulates the default prompts but uses fancy Unicode:# Changes during a session; use function. # "tilde-abbr" abbreviates home directory to a tilde. le:prompt={ put `tilde-abbr $pwd`'❱ ' } # Doesn't change during a session; use constant string. le:rprompt=`whoami`✸`hostname`
-
Press Up to search through history. It uses what you have typed to do prefix match. To cancel, press Escape.
-
Press Tab to start completion. Use arrow key and Tab to select the candidate; press Enter, or just continue typing to accept. To cancel, press Enter.
-
Press Ctrl-N to start navigation mode. Press Ctrl-H to show hidden files; press again to hide. Likewise, pressing Escape gets you back to the default (insert) mode.
-
Try typing
echo [
and press Enter. Elvish knows that the command is unfinished due to the unclosed[
and inserts a newline instead of accepting the command. Moreover, common errors like syntax errors and missing variables are highlighted in real time. -
Elvish remembers which directories you have visited. Press Ctrl-L to list visited directories. Like in completion, use Up, Down and Tab to navigate and use Enter to accept (which
cd
s into the selected directory). Press Escape to cancel.Type to filter:
The filtering algorithm takes your filter and adds
**
to both sides of each path component. Sog/ed
becomes pattern**g**/**ed**
, so it matches /home/xiaq/go/elves/elvish/edit. -
NOTE: Default key bindings as listed above are subject to change in the future; but the functionality will not go away.
-
Elvish doesn't support history expansion like
!!
. Instead, it has a "bang mode", trigerred byAlt-,
, that provides the same functionality. For example, if you typed a command but forgot to addsudo
, you can then typesudo
and pressAlt-,
twice to fix it: -
Lists look like
[a b c]
, and maps look like[&key1=value1 &key2=value2]
. Unlike other shells, lists never expands to multiple words, unless you explicitly splice it by prefixing the variable name with$@
:~> li=[1 2 3] ~> for x in $li; do echo $x; done [1 2 3] ~> for x in $@li; do echo $x; done 1 2 3
-
You can manipulate search paths through the special list
$paths
:~> echo $paths [/bin /sbin] ~> paths=[/opt/bin $@paths /usr/bin] ~> echo $paths [/opt/bin /bin /sbin /usr/bin] ~> echo $env:PATH /opt/bin:/bin:/sbin:/usr/bin
-
You can manipulate the keybinding through the map
$le:binding
. For example, this binds Ctrl-L to clearing the terminal:le:binding[insert][Ctrl-L]={ clear > /dev/tty }
. The first index is the mode and the second is the key. (Yes, the braces enclose a lambda.)Use
pprint $le:binding
to get a nice (albeit long) view of the current keybinding. -
Environment variables live in a separate
env:
namespace and must be explicitly qualified:~> put $env:HOME ▶ /home/xiaq ~> env:PATH=$env:PATH":/bin"
-
There is no interpolation inside double quotes (yet). Use implicit string concatenation:
~> name=xiaq ~> echo "My name is "$name"." My name is xiaq.
-
Elementary floating-point arithmetics as well as comparisons are builtin. Unfortunately, you have to use prefix notation:
~> + 1 2 ▶ 3 ~> div `mul 2 3` 4 # div for /, mul for * ▶ 1.5 ~> div (mul 2 3) 4 # parentheses are equivalent to backquotes, but look nicer in arithmetics ▶ 1.5 ~> gt 1 2 # gt for > false ~> lt 1 2 # lt for <; silence means "true"
-
Functions are defined with
fn
. You can name arguments:~> fn square [x]{ mul $x $x } ~> square 4 ▶ 16
-
Output of some builtin commands start with a funny "▶". It is not part of the output itself, but shows that such commands output a stream of values instead of bytes. As such, their internal structures as well as boundaries between valued are preserved. This allows us to manipulate structured data in the shell; more on this later.
Building Elvish
Go >= 1.5 is required. Linux is fully supported. It is likely to work on BSDs and Mac OS X. Windows is not supported yet.
The main binary can be installed using go get github.com/elves/elvish
. There is also an auxiliary program called elvish-stub; install it with make stub
. Elvish is funtional without the stub, but job control features depend on it.
If you are lazy and use bash
for zsh
now, here is something you can copy-paste into your terminal:
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
mkdir -p $GOPATH
go get github.com/elves/elvish
make -C $GOPATH/src/github.com/elves/elvish stub
for f in ~/.bashrc ~/.zshrc; do
echo -e 'export GOPATH=$HOME/go\nexport PATH=$PATH:$GOPATH/bin' >> $f
done
How To Write Go Code explains how $GOPATH
works.
Name
In roguelikes, items made by the elves have a reputation of high quality. These are usually called elven items, but I chose elvish for an obvious reason.
The adjective for elvish is also "elvish", not "elvishy" and definitely not "elvishish".
Test coverages:
Package | Coverage |
---|---|
edit | |
eval | |
glob | |
parse | |
run | |
store | |
stub | |
sys | |
util |