Quick Tour

Table of Content:

1 Introduction

Welcome to the quick tour of Elvish. This tour works best if you have used another shell or programming language before.

If you are mainly interested in using Elvish interactively, jump directly to interactive features.

2 Basic shell language

Many basic language features of Elvish are very familiar to traditional shells. A notable exception is control structures, covered below in the advanced language features section.

2.1 Comparison with bash

If you are familiar with bash, the following table shows some (rough) correspondance between Elvish and bash syntax:

Feature Elvish bash equivalent
Barewords echo foo
Single-quoted strings echo 'foo'
echo 'It''s good' echo 'It'\''s good'
Double-quoted strings echo "foo"
echo "foo\nbar" echo $'foo\nbar'
echo "foo: "$foo echo "foo: $foo"
Comments # comment
Line continuation echo foo ^
echo foo \
Brace expansion echo {foo bar}.txt echo {foo,bar}.txt
Wildcards echo *.?
echo **.go find . -name '*.go'
echo *.?[set:ch] echo *.[ch]
Tilde expansion echo ~/foo
Setting variables var foo = bar foo=bar
set foo = bar foo=bar
foo=bar cmd
Using variables echo $foo
echo $E:HOME echo $HOME
Redirections head -n10 < a.txt > b.txt
Byte pipelines head -n4 a.txt | grep x
Output capture ls -l (which elvish) ls -l $(which elvish)
Background jobs echo foo &

2.2 Barewords

Like traditional shells, unquoted words that don’t contain special characters are treated as strings (such words are called barewords):

~> echo foobar
~> ls /
bin dev home lib64 mnt proc run srv tmp var
boot etc lib lost+found opt root sbin sys usr
~> vim a.c

This is one of the most distinctive syntactical features of shells; non-shell programming languages typically treat unquoted words as names of functions and variables.

Read the language reference on barewords to learn more.

2.3 Single-quoted strings

Like traditional shells, single-quoted strings expand nothing; every character represents itself (except the single quote itself):

~> echo 'hello\world$'

Like Plan 9 rc, or zsh with the RC_QUOTES option turned on, the single quote itself can be written by doubling it

~> echo 'it''s good'
it's good

Read the language reference on single-quoted strings to learn more.

2.4 Double-quoted strings

Like many non-shell programming languages and $'' in bash, double-quoted strings support C-like escape sequences (e.g. \\n for newline):

~> echo "foo\nbar"

Unlike traditional shells, Elvish does not support interpolation inside double-quoted strings. Instead, you can just write multiple words together, and they will be concatenated:

~> var x = foo
~> put 'x is '$x
▶ 'x is foo'

Read the language reference on double-quoted strings to learn more.


Comments start with # and extends to the end of the line:

~> echo foo # this is a comment

2.6 Line continuation

Line continuation in Elvish uses ^ instead of \:

~> echo foo ^
foo bar

Unlike traditional shells, line continuation is treated as whitespace. In Elvish, the following code outputs foo bar:

echo foo^

However, in bash, the following code outputs foobar:

echo foo\

2.7 Brace expansion

Brace expansions in Elvish work like in traditional shells, but use spaces instead of commas:

~> echo {foo bar}.txt
foo.txt bar.txt

The opening brace { must not be followed by a whitespace, to disambiguate from lambdas.

Note: commas might still work as a separator in Elvish’s brace expansions, but it will eventually be deprecated and removed soon.

Read the language reference on braced lists to learn more.

2.8 Wildcards

The basic wildcard characters, * and ?, work like in traditional shells:

~> ls
bar.ch d1 d2 d3 foo.c foo.h lorem.go lorem.txt
~> echo *.?
foo.c foo.h

Elvish also supports **, which matches multiple path components:

~> find . -name '*.go'
~> echo **.go
d1/a.go d2/b.go d3/d4/c.go lorem.go

Character classes are a bit more verbose in Elvish. First, a character set is written like [set:ch], instead of just [ch]. Second, they don’t appear on their own, but as a suffix to ?. For example, to match files ending in either .c or .h, use:

~> echo *.?[set:ch]
foo.c foo.h

A character set suffix can also be applied to *. For example, to match files who extension only contains c and h:

~> echo *.*[set:ch]
bar.ch foo.c foo.h

Read the language reference on wildcard expansion to learn more.

2.9 Tilde expansion

Tilde expansion works likes in traditional shells. Assuming that the home directory of the current user is /home/me, and the home directory of elf is /home/elf:

~> echo ~/foo
~> echo ~elf/foo

Read the language reference on tilde expansion to learn more.

2.10 Setting variables

Variables are declared with the var command, and set with the set command:

~> var foo = bar
~> echo $foo
~> set foo = quux
~> echo $foo

The spaces around = are mandatory.

Unlike traditional shells, variables must be declared before they can be set; setting an undeclared variable results in an error.

Like traditional shells, Elvish supports setting a variable temporarily for the duration of a command, by prefixing the command with foo=bar. For example:

~> var foo = original
~> fn f { echo $foo }
~> foo=temporary f
~> echo $foo

Read the language reference on the var command, the set command and temporary assignments to learn more.

2.11 Using variables

Like traditional shells, using the value of a variable requires the $ prefix.

~> var foo = bar
~> echo $foo

Unlike traditional shells, variables must be declared before being used; if the foo variable wasn’t declared with var first, echo $foo results in an error.

Also unlike traditional shells, environment variables in Elvish live in a separate E: namespace:

~> echo $E:HOME

Read the language reference on variables and special namespaces to learn more.

2.12 Redirections

Redirections in Elvish work like in traditional shells. For example, to save the first 10 lines of a.txt to a1.txt:

~> head -n10 < a.txt > a1.txt

Read the language reference on redirections to learn more.

2.13 Byte pipelines

UNIX pipelines in Elvish (called byte pipelines, to distinguish from value pipelines) work like in traditional shells. For example, to find occurrences of x in the first 4 lines of a.txt:

~> cat a.txt
~> head -n4 a.txt | grep x

Read the language reference on pipelines to learn more.

2.14 Output capture

Output of commands can be captured and used as values with (). For example, the following command shows details of the elvish binary:

~> ls -l (which elvish)
-rwxr-xr-x 1 xiaq users 7813495 Mar 2 21:32 /home/xiaq/go/bin/elvish

Note: the same feature is usually known as command substitution in traditonal shells.

Unlike traditional shells, Elvish only splits the output on newlines, not any other whitespace characters.

Read the language reference on output capture to learn more.

2.15 Background jobs

Add & to the end of a pipeline to make it run in the background, similar to traditional shells:

~> echo foo &
job echo foo & finished

Unlike traditional shells, the & character does not serve to separate commands. In bash you can write echo foo & echo bar; in Elvish you still need to terminate the first command with ; or newline: echo foo &; echo bar.

Read the language reference on background pipelines to learn more.

3 Advanced language features

Building on a core of familiar shell-like syntax, the Elvish language incorporates many advanced features that make it a modern dynamic programming language.

3.1 Value output

Like in traditional shells, commands in Elvish can output bytes. The echo command outputs bytes:

~> echo foo bar
foo bar

Additionally, commands can also output values. Values include not just strings, but also lambdas, numbers, lists and maps. The put command outputs values:

~> put foo [foo] [&foo=bar] { put foo }
▶ foo
▶ [foo]
▶ [&foo=bar]
▶ <closure 0xc000347500>

Many builtin commands output values. For example, string functions in the str: module outputs their results as values. This makes those functions work seamlessly with strings that contain newlines or even NUL bytes:

~> use str
~> str:join ',' ["foo\nbar" "lorem\x00ipsum"]
▶ "foo\nbar,lorem\x00ipsum"

Unlike most programming languages, Elvish commands don’t have return values. Instead, they use the value output to “return” their results.

Read the reference for builtin commands to learn which commands work with value inputs and outputs. Among them, here are some general-purpose primitives:

Command Functionality
all Passes value inputs to value outputs
each Applies a function to all values from value intput
put Writes arguments as value outputs
slurp Convert byte input to a single string in value output

3.2 Value pipelines

Pipelines work with value outputs too. When forming pipelines, a command that writes value outputs can be followed by a command that takes value inputs. For example, the each command takes value inputs, and applies a lambda to each one of them:

~> put foo bar | each [x]{ echo 'I got '$x }
I got foo
I got bar

Read the language reference on pipelines to learn more about pipelines in general.

3.3 Value output capture

Output capture works with value output too. Capturing value outputs always recovers the exact values there were written. For example, the str:join command joins a list of strings with a separator, and its output can be captured and saved in a variable:

~> use str
~> var s = (str:join ',' ["foo\nbar" "lorem\x00ipsum"])
~> put $s
▶ "foo\nbar,lorem\x00ipsum"

Read the language reference on output capture to learn more.

3.4 Lists and maps

Lists look like [a b c], and maps look like [&key1=value1 &key2=value2]:

~> var li = [foo bar lorem ipsum]
~> put $li
▶ [foo bar lorem ipsum]
~> var map = [&k1=v2 &k2=v2]
~> put $map
▶ [&k1=v2 &k2=v2]

You can get elements of lists and maps by indexing them. Lists are zero-based and support slicing too:

~> put $li[0]
▶ foo
~> put $li[1..3]
▶ [bar lorem]
~> put $map[k1]
▶ v2

Read the language reference on lists and maps to learn more.

3.5 Numbers

Elvish has a double-precision floating-point number type, float64. There is no dedicated syntax for it; instead, it can constructed using the float64 builtin:

~> float64 1
▶ (float64 1)
~> float64 1e2
▶ (float64 100)

Most arithmetic commands in Elvish support both typed numbers and strings that can be converted to numbers. They usually output typed numbers:

~> + 1 2
▶ (float64 3)
~> use math
~> math:pow (float64 10) 3
▶ (float64 1000)

Note: The set of number types will likely expand in future.

Read the language reference on numbers and the reference for the math module to learn more.

3.6 Booleans

Elvish has two boolean values, $true and $false.

Read the language reference on booleans to learn more.

3.7 Options

Many Elvish commands take options, which look like map pairs (&key=value). For example, the echo command takes a sep option that can be used to override the default separator of space:

~> echo &sep=',' foo bar
~> echo &sep="\n" foo bar

3.8 Lambdas

Lambdas are first-class values in Elvish. They can be saved in variables, used as commands, passed to commands, and so on.

Lambdas can be written by enclosing its body with { and }:

~> var f = { echo "I'm a lambda" }
~> $f
I'm a lambda
~> put $f
▶ <closure 0xc000265bc0>
~> var g = (put $f)
~> $g
I'm a lambda

The opening brace { must be followed by some whitespace, to disambiguate from brace expansion.

Lambdas can take arguments and options, which can be written in a signature:

~> var f = [a b &opt=default]{
echo "a = "$a
echo "b = "$b
echo "opt = "$opt
~> $f foo bar
a = foo
b = bar
opt = default
~> $f foo bar &opt=option
a = foo
b = bar
opt = option

There must be no space between the ] and { in this case.

Read the language reference on functions to learn more about functions.

3.9 Control structures

Control structures in Elvish look very different from traditional shells. For example, this is how an if command looks:

~> if (eq (uname) Linux) { echo "You're on Linux" }
You're on Linux

The if command takes a conditional expression (an output capture in this case), and the body to execute as a lambda. Since lambdas allow internal newlines, you can also write it like this:

~> if (eq (uname) Linux) {
echo "You're on Linux"
You're on Linux

However, you must write the opening brace { on the same line as if. If you write it on a separate line, Elvish would parse it as two separate commands.

The for command looks like this:

~> for x [expressive versatile] {
echo "Elvish is "$x
Elvish is expressive
Elvish is versatile

Read the language reference on the if command, the for command, and additionally the while command to learn more.

3.10 Exceptions

Elvish uses exceptions to signal errors. For example, calling a function with the wrong number of arguments throws an exception:

~> var f = { echo foo } # doesn't take arguments
~> $f a b
Exception: arity mismatch: arguments here must be 0 values, but is 2 values
[tty 2], line 1: $f a b

Moreover, non-zero exits from external commands are also turned into exceptions:

~> false
Exception: false exited with 1
[tty 3], line 1: false

Exceptions can be caught using the try command:

~> try {
} except e {
echo 'got an exception'
got an exception

Read the language reference on the exception value type and the try command to learn more.

3.11 Namespaces and modules

The names of variables and functions can have namespaces prepended to their names. Namespaces always end with :.

The using variables section has already shown the E: namespace. Other namespaces can be added by importing modules with use. For example, the str: module provides string utilities:

~> use str
~> str:to-upper foo

You can define your own modules by putting .elv files in ~/.elvish/lib. For example, to define a module called foo, put the following in ~/.elvish/lib/foo.elv:

fn f {
echo 'in a function in foo'

This module can now be used like this:

~> use foo
~> foo:f
in a function in foo

Read the language reference on namespaces and modules to learn more.

3.12 External command support

As shown in examples above, Elvish supports calling external commands directly by writing their name. If an external command exits with a non-zero code, it throws an exception.

Unfortunately, many of the advanced language features are only available for internal commands and functions. For example:

  • They can only write byte output, not value output.

  • They only take string arguments; non-string arguments are implicitly coerced to strings.

  • They don’t take options.

Read the language reference on ordinary commands to learn more about when Elvish decides that a command is an external command.

4 Interactive features

4.1 Tab completion

Press Tab to start completion. For example, after typing vim and Space, press Tab to complete filenames:

~/on/elvish> vim 0.16.0-release-notes.md     xiaq@macarch
0.16.0-release-notes.md LICENSE README.md go.sum
CONTRIBUTING.md Makefile cmd/ pkg/
Dockerfile PACKAGING.md go.mod tools/

Basic operations should be quite intuitive:

  • To navigate the candidate list, use arrow keys or Tab and Shift-Tab.

  • To accept the selected candidate, press Enter.

  • To cancel, press Escape.

As indicated by the horizontal scrollbar, you can scroll to the right to find additional results that don’t fit in the terminal.

You may have noticed that the cursor has moved to the right of “COMPLETING argument”. This indicates that you can continue typing to filter candidates. For example, after typing .md, the UI looks like this:

~/on/elvish> vim 0.16.0-release-notes.md     xiaq@macarch
COMPLETING argument .md
0.16.0-release-notes.md PACKAGING.md

Read the reference on completion API to learn how to program and customize tab completion.

4.2 Command history

Elvish has several UI features for working with command history.

4.2.1 History walking

Press to fetch the last command. This is called history walking mode:

~> vim .elvish/rc.elv                        xiaq@macarch
HISTORY #56395

Press to go further back, to go forward, or Escape to cancel.

To restrict to commands that start with a prefix, simply type the prefix before pressing . For example, to walk through commands starting with echo, type echo before pressing :

~> echo foo bar                              xiaq@macarch
HISTORY #55830

4.2.2 History listing

Press Ctrl-R to list the full command history:

~>                                           xiaq@macarch
HISTORY (dedup on)
56439 cd ~
56440 ls
56441 echo foo bar
56442 vim .elvish/rc.elv

Like in completion mode, type to filter the list, press and to navigate the list, Enter to insert the selected entry, or Escape to cancel.

4.2.3 Last command

Finally, Elvish has a last command mode dedicated to inserting parts of the last command. Press Alt-, to trigger it:

~>                                           xiaq@macarch
vim .elvish/rc.elv
0 vim
1 .elvish/rc.elv

4.3 Directory history

Elvish remembers which directories you have visited. Press Ctrl-L to list visited directories. Use and to navigate the list, Enter to change to that directory, or Escape to cancel.

~>                                           xiaq@macarch
103 ~/go/src/github.com/elves/elvish/website
49 ~/go/src/github.com/elves/elvish/website/learn
47 ~/go/src/github.com/elves/elvish
27 ~/go/src/github.com/elves/elvish/pkg

Type to filter:

~>                                           xiaq@macarch
19 ~/go/src/github.com/elves/elvish/pkg/parse
7 ~/go/src/github.com/elves/elvish/pkg/parse/cmpd
1 ~/go/src/github.com/elves/elvish/pkg/parse/expr
1 ~/go/src/github.com/elves/elvish/pkg/parse/parseutil

Press Ctrl-N to start the builtin filesystem navigator, or navigation mode.

~/go/src/github.com/elves/elvish>            xiaq@macarch
cirr 0.16.0-release-not This is the draft release n
elvi CONTRIBUTING.md 2020-07-01.
posi Dockerfile
up LICENSE # Breaking changes
NEXT-RELEASE.md - The following commands
PACKAGING.md `edit:close-listing`, `
README.md `edit:listing:close`.
cover - The `edit:histlist:togg

Unlike other modes, the cursor stays in the main buffer in navigation mode. This allows you to continue typing commands; while doing that, you can press Enter to insert the selected filename. You can also press Alt-Enter to insert the filename without exiting navigation mode; this is useful when you want to insert multiple filenames.

4.5 Startup script

Elvish’s startup script is ~/.elvish/rc.elv.

Elvish doesn’t support aliases, but you can get a similar experience simply by defining functions:

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. The following configuration 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))

This is how it looks:

~❱ # Fancy unicode prompts!                  xiaq✸xiaqsmbp

Another common task in the startup script is to set the search path. You can do it directly via $E:PATH, but you can also manipulate as a list in $paths:

set paths = [/opts/bin /bin /usr/bin]

Read the API of the interactive editor to learn more about UI customization options.