edit | ||
eval | ||
glob | ||
parse | ||
run | ||
store | ||
stub | ||
stubimpl | ||
sys | ||
util | ||
.gitattributes | ||
.gitignore | ||
.travis.yml | ||
CONTRIBUTING.md | ||
Dockerfile | ||
LICENSE | ||
main.go | ||
Makefile | ||
README.md |
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
64-bit Linux: curl -s https://dl.elvish.io/elvish-linux.tar.gz | sudo tar vxz -C /usr/bin
64-bit Mac OS X: curl -s https://dl.elvish.io/elvish-osx.tar.gz | sudo tar vxz -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 Escape.
-
Press Ctrl-N to start navigation mode. Press Ctrl-H to show hidden files; press again to hide. Press tab to append selected filename to your command. 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/di
becomes pattern**g**/**di**
, 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 ~> / `* 2 3` 4 ▶ 1.5 ~> / (* 2 3) 4 # parentheses are equivalent to backquotes, but look nicer in arithmetics ▶ 1.5 ~> > 1 2 # ">" may be used as a command name false ~> < 1 2 # "<" may also be used as a command name; silence means "true"
-
Functions are defined with
fn
. You can name arguments:~> fn square [x]{ * $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
printf 'export %s=%s\n' GOPATH '$HOME/go' 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".