Table of Content:

If you come from other shells, hopefully the following recipes will get you started quickly:

UI Recipes

  • Put your startup script in ~/.elvish/rc.elv. There is no alias yet, but you can achieve the goal by defining a function:

    fn ls [@a]{ e:ls --color $@a }

    The e: prefix (for “external”) ensures that the external command named ls will be called. Otherwise this definition will result in infinite recursion.

  • The left and right prompts can be customized by assigning functions to edit:prompt and edit:rprompt. Their outputs are concatenated (with no spaces in between) before being used as the respective prompts. The following simulates the default prompts but uses fancy Unicode:

    # "tilde-abbr" abbreviates home directory to a tilde.
    edit:prompt = { tilde-abbr $pwd; put '❱ ' }
    # "constantly" returns a function that always writes the same value(s) to
    # output; "styled" writes styled output.
    edit:rprompt = (constantly (styled (whoami)(hostname) inverse))

    Here is a terminalshot of the alternative prompts:

    ~❱ # Fancy unicode prompts!                  xiaq✸xiaqsmbp
  • Press ▲︎ to search through history. It uses what you have typed to do prefix match. To cancel, press Escape.

    ~> echo $daemon:pid                          xiaq@xiaqsmbp
    HISTORY #11610
  • Press Tab to start completion. Use arrow keys ▲︎ ▼︎ ◀︎ ▶︎ or Tab and Shift-Tab to select the candidate. Press Enter, or just continue typing to accept. To cancel, press Escape. It even comes with a scrollbar! :) In fact, all interactive modes show a scrollbar when there is more output to see.

    ~/go/src/> vim 
    COMPLETING argument ━━━━━━━━━━━━ Gopkg.toml edit getopt
    Dockerfile LICENSE cover errors glob
    Gopkg.lock Makefile daemon eval main.go
  • You can make completion case-insensitive with the following code:

    edit:-matcher[''] = [p]{ edit:match-prefix &ignore-case $p }

    You can also make the completion use “smart case” by changing &ignore-case to &smart-case. This means that if your pattern is entirely lower case it ignores case, otherwise it’s case sensitive.

  • Press Ctrl-N to start the builtin filesystem navigator, aptly named “navigation mode.” Use arrow keys to navigate. Enter inserts the selected filename to your command line. If you want to insert the filename and stay in the mode (e.g. when you want to insert several filenames), use Alt-Enter.

    You can continue typing your command when you are in navigation mode. Press Ctrl-H to toggle hidden files; and like in other modes, Escape gets you back to the default (insert) mode.

    ~/go/src/>            xiaq@xiaqsmbp
    elvish FROM golang:onbuild
    fix-for-0.7 Dockerfile
    images Gopkg.lock
    md-highlighter Gopkg.toml
  • 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 arrow keys ▲︎ ▼︎ or Tab and Shift-Tab to select a directory and use Enter to cd into it. Press Escape to cancel.

    ~>                                           xiaq@xiaqsmbp
    * ~
    * ~/go/src/
    110 ~/on/elvish-site/code
    62 ~/on/elvish-site/code/src
    52 ~/go/src/
    34 ~/on/elvish-site/code/tty
    33 ~/on/elvish-site/code/assets
    32 ~/go/src/
    26 ~/on/chat-app/code
    24 ~/on/elvish-site/code/dst
    20 ~/go/src/
    14 ~/on/chat-app/code/public
    13 ~/.elvish

    Type to filter:

    ~>                                           xiaq@xiaqsmbp
    LOCATION x/p/v
    1 ~/go/src/

    The filtering algorithm is tailored for matching paths; you need only type a prefix of each component. In the screenshot, x/p/v matches xiaq/persistent/vector.

  • Elvish doesn’t support history expansion like !!. Instead, it has a “last command mode” offering the same functionality, triggered by Alt-1 by default (resembling how you type ! using Shift-1). In this mode, you can pick individual arguments from the last command using numbers, or the entire command by typing Alt-1 again.

    This is showing me trying to fix a forgotten sudo:

    ~> rm -rf /var
    rm: /var: Operation not permitted
    Exception: rm exited with 1
    [interactive], line 1:
    rm -rf /var
    ~> xiaq@xiaqsmbp
    M-1 rm -rf /var
    0 rm
    1 -rf
    2 /var

Language Recipes

  • Lists look like [a b c], and maps look like [&key1=value1 &key2=value2]. Unlike other shells, a list never expands to multiple words, unless you explicitly explode it by prefixing the variable name with @:

    ~> li = [1 2 3]
    ~> put $li
    ▶ [1 2 3]
    ~> put $@li
    ▶ 1
    ▶ 2
    ▶ 3
    ~> map = [&k1=v1 &k2=v2]
    ~> echo $map[k1]
  • Environment variables live in a separate E: (for “environment”) namespace and must be explicitly qualified:

    ~> put $E:HOME
    ▶ /home/xiaq
    ~> E:PATH = $E:PATH":/bin"
  • You can manipulate search paths through the special list $paths, which is synced with $E:PATH:

    ~> echo $paths
    [/bin /sbin]
    ~> paths = [/opt/bin $@paths /usr/bin]
    ~> echo $paths
    [/opt/bin /bin /sbin /usr/bin]
    ~> echo $E:PATH
  • You can manipulate the keybinding in the default insert mode through the map $edit:insert:binding. For example, this binds Ctrl-L to clearing the terminal:

    edit:insert:binding[Ctrl-L] = { clear > /dev/tty }

    Use pprint $edit:insert:binding to get a nice (albeit long) view of the current keybinding.

    NOTE: Bindings for letters modified by Alt are case-sensitive. For instance, Alt-a means pressing Alt and A, while Alt-A means pressing Alt, Shift and A. This will probably change in the future.

  • There is no interpolation inside double quotes (yet). For example, the output of echo "$user" is simply the string $user. Use implicit string concatenation to build strings:

    ~> name = xiaq
    ~> echo "My name is "$name"."
    My name is xiaq.

    Sometimes string concatenation will force you to use string literals instead of barewords:

    ~> noun = language
    ~> echo $noun's'

    You cannot write s as a bareword because Elvish would think you are trying to write the variable $nouns. It’s hard to make such mistakes when working interactively, as Elvish highlights variables and complains about nonexistent variables as you type.

  • Double quotes support C-like escape sequences (\n for newline, etc.):

    ~> echo "a\nb"

    NOTE: If you run echo "a\nb" in bash or zsh, you might get the same result (depending on the value of some options), and this might lead you to believe that they support C-like escape sequences in double quotes as well. This is not the case; bash and zsh preserve the backslash in double quotes, and it is the echo builtin command that interpret the escape sequences. This difference becomes apparent if you change echo to touch: In Elvish, touch "a\nb" creates a file whose name has a newline; while in bash or zsh, it creates a file whose name contains a backslash followed by n.

  • Elementary floating-point arithmetic as well as comparisons are builtin, with a prefix syntax:

    ~> + 1 2
    ▶ 3
    ~> / (* 2 3) 4
    ▶ 1.5
    ~> > 1 2
    ▶ $false
    ~> < 1 2
    ▶ $true

    NOTE: Elvish has special parsing rules to recognize < and > as command names. That means that you cannot put redirections in the beginning; bash and zsh allows < input cat, which is equivalent to cat < input; in Elvish, you can only use the latter syntax.

  • 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 values are preserved. This allows us to manipulate structured data in the shell.

    Read unique semantics for details.

  • When calling Elvish commands, options use the special &option=value syntax. For instance, the echo builtin command supports a sep option for specifying an alternative separator:

    ~> echo &sep="," a b c

    The mixture of options and arguments is a classical problem in traditional shell programming. For instance, if you want to print a file whose name is in $x, cat $x is the obvious thing to do, but it does not do this reliably – if $x starts with - (e.g. -v), cat thinks that it is an option. The correct way is cat -- $x.

    Elvish commands are free from this problem. However, the option facility is only available to builtin commands and user-defined functions, not external commands, meaning that you still need to do cat -- $x.

    In principle, it is possible to write safe wrapper for external commands and there is a plan for this.