- 1. Introduction
- 2. Basic shell language
- 2.1. Comparison with bash
- 2.2. Barewords
- 2.3. Single-quoted strings
- 2.4. Double-quoted strings
- 2.5. Comments
- 2.6. Line continuation
- 2.7. Brace expansion
- 2.8. Wildcards
- 2.9. Tilde expansion
- 2.10. Variables
- 2.11. Environment variables
- 2.12. Redirections
- 2.13. Byte pipelines
- 2.14. Output capture
- 2.15. Background jobs
- 2.16. Command sequence
- 3. Advanced language features
- 4. Interactive features
- 5. Shell scripting commands
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 familiar with traditional shells like Bash, the sections basic shell language and shell scripting commands can help you “translate” your knowledge into Elvish.
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
The following table shows some (rough) correspondence 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 |
|
Variables | echo $foo |
|
var foo = bar |
foo=bar |
|
set foo = bar |
foo=bar |
|
{ tmp foo = bar; some-command } |
foo=bar some-command |
|
Environment variables | echo $E:HOME |
echo $HOME |
set E:foo = bar |
export foo=bar |
|
{ tmp E:foo = bar; some-command } |
export foo; foo=bar some-command |
|
env foo=bar some-command |
||
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 & |
|
Command sequence | a; b |
a && b |
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
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$'
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, like \n
for newline:
~> echo "foo\nbar"
foo
bar
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.
2.5. Comments
Comments start with #
and extend to the end of the line:
~> echo foo # this is a comment
foo
2.6. Line continuation
Line continuation in Elvish uses ^
instead of \
:
~> echo foo ^
bar
foo bar
Unlike traditional shells, line continuation is treated as whitespace. In
Elvish, the following code outputs foo bar
:
echo foo^
bar
However, in bash, the following code outputs foobar
:
echo foo\
bar
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'
./d1/a.go
./d2/b.go
./lorem.go
./d3/d4/c.go
~> echo **.go
d1/a.go d2/b.go d3/d4/c.go lorem.go
Character classes are a bit more verbose in Elvish:
-
They don’t appear on their own, but as a suffix to
?
; -
A character set is written like
[set:ch]
, instead of just[ch]
.
For example, to match files ending in either .c
or .h
, use:
~> echo *.?[set:ch]
foo.c foo.h
The suffix syntax means that they 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
/home/me/foo
~> echo ~elf/foo
/home/elf/foo
Read the language reference on tilde expansion to learn more.
2.10. Variables
Like traditional shells, using the value of a variable requires the $
prefix.
~> var foo = bar
~> echo $foo
bar
2.10.1. Field splitting
Elvish does not perform $IFS
splitting on variables, so $foo
always
evaluates to one value, even if it contains whitespaces and newlines:
~> var foo = 'a b c d'
~> touch $foo # Creates one file
You never need to write "$foo"
in Elvish. In fact,
double-quoted strings do not support interpolation in
Elvish, so echo "$foo"
will just print out $foo
).
If you do need to split fields, you can either do this explicitly with
str:fields
, or store a list of strings and
“explode” it with $@
:
~> var args-as-string = 'a b c d'
~> use str
~> touch (str:fields $args-as-string) # creates four files
~> var args-as-list = [a b c d]
~> touch $@args-as-list # also creates four files
2.10.2. Declaring and setting variables
Also 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.
After declaring a variable, change its value with set
:
~> var foo = bar
~> echo $foo
bar
~> set foo = quux
~> echo $foo
quux
The spaces around =
in both var
and set
are mandatory.
Within a lambda, you can use tmp
to set the value for the duration
of the lambda:
~> var foo = bar
~> { tmp foo = new; echo $foo }
new
~> echo $foo
bar
Read the language reference on variables,
variable use,
the var
command,
the set
command and
the tmp
command to learn more.
2.11. Environment variables
Unlike traditional shells, environment variables in Elvish live in a separate
E:
namespace:
~> echo $E:HOME
/home/elf
~> set E:PATH = /bin:/sbin
There is no concept of “exporting” in Elvish: variables in the E:
namespace
are always “exported”, and variables outside the namespace never are.
Accessing unset environment variables results in an empty string:
~> echo $E:nonexistent
Elvish also provides a series of builtin commands (set-env
, unset-env
,
has-env
and get-env
) that allows you to distinguish unset environment
variables and those set to an empty string.
To set an environment variable temporarily, you can use the tmp
command like
you would with a non-environment variable, but it is more concise to use the
external command env
.
~> { tmp E:foo = bar; bash -c 'echo $foo' }
bar
~> env foo=bar bash -c 'echo $foo'
bar
Read the language reference on the
E:
namespace, the
set-env
,
unset-env
,
has-env
and
get-env
builtin commands 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
foo
barx
lorem
quux
lux
nox
~> head -n4 a.txt | grep x
barx
quux
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 &
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.
2.16. Command sequence
Join commands with a ;
or newline to run them sequentially (insert a newline
with Alt-Enter):
~> echo a; echo b
a
b
~> echo a
echo b
a
b
In Elvish, when a command fails (e.g. when an external command exits with a non-zero status), execution gets terminated.
~> echo before; false; echo after
before
Exception: false exited with 1
[tty 2], line 1: echo before; false; echo after
In this aspect, Elvish’s behavior is similar to joining all commands with &&
or setting set -e
in traditional shells:
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 input |
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 number type. There is no dedicated syntax for it; instead, it can
constructed using the num
builtin:
~> num 1
▶ (num 1)
~> num 1e2
▶ (num 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
▶ (num 3)
~> use math
~> math:pow (num 10) 3
▶ (num 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
foo,bar
~> echo &sep="\n" foo bar
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
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 {
false
} catch 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 environment 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
▶ FOO
You can define your own modules by putting .elv
files in
~/.config/elvish/lib
(or ~\AppData\Roaming\elvish\lib
). For example, to
define a module called foo
, put the following in foo.elv
under the
aforementioned directory:
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
Read the API of the interactive editor to learn more about UI customization options.
4.1. Tab completion
Press Tab to start completion. For example, after typing vim
and
Space, press Tab to complete filenames:
~> cd elvish
~/elvish> echo 1.0-release.md elf@host
COMPLETING argument
1.0-release.md README.md syntaxes/
CONTRIBUTING.md SECURITY.md tools/
Dockerfile cmd/ vscode/
LICENSE go.mod website/
Makefile go.sum
PACKAGING.md pkg/
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:
~> cd elvish
~/elvish> echo 1.0-release.md elf@host
COMPLETING argument .md
1.0-release.md PACKAGING.md SECURITY.md
CONTRIBUTING.md README.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:
~> math:min 3 1 30 elf@host
Ctrl-A autofix: use math Tab Enter autofix first
HISTORY #32
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 (styled warning: red) bumpy road elf@host
HISTORY #2
4.2.2. History listing
Press Ctrl-R to list the full command history:
~> elf@host
HISTORY (dedup on) Ctrl-D dedup
3 echo "hello\nbye" > /tmp/x │
4 from-lines < /tmp/x │
5 cd /tmp
6 cd ~/elvish
7 git branch
8 git checkout .
9 git commit
19 git status
20 cd /usr/local/bin
21 echo $pwd
22 * (+ 3 4) (- 100 94)
31 make
32 math:min 3 1 30
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:
~> echo abc def
abc def
~> vim elf@host
LASTCMD
echo abc def
0 echo
1 abc
2 def
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.
~> elf@host
LOCATION
10 ~/elvish
10 ~/.local/share/elvish
10 ~/elvish/website
10 ~/.config/elvish
9 ~/elvish/pkg/edit
9 ~/elvish/pkg/eval
9 /opt
9 /usr/local
9 /usr/local/share
9 /usr/local/bin
9 /usr
9 /tmp │
8 ~/zsh │
Type to filter:
~> elf@host
LOCATION local
10 ~/.local/share/elvish
9 /usr/local
9 /usr/local/share
9 /usr/local/bin
4.4. Navigation mode
Press Ctrl-N to start the builtin filesystem navigator.
~/elvish> elf@host
NAVIGATING Ctrl-H hidden Ctrl-F filter
bash 1.0-release.m 1.0 has not been released y
elvis CONTRIBUTING.
zsh Dockerfile
LICENSE
Makefile
PACKAGING.md
README.md
SECURITY.md
cmd
go.mod
go.sum
pkg │
syntaxes │
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 interactive startup script is rc.elv
.
Non-interactive Elvish sessions do not have a startup script.
4.5.1. POSIX aliases
Elvish doesn’t support POSIX 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.
4.5.2. Prompt customization
The left and right prompts can be customized by assigning functions to
edit:prompt
and
edit:rprompt
. The following example defines
prompts similar to the default, but uses fancy Unicode.
~> set edit:rprompt = (constantly ^
(styled (whoami)✸(hostname) inverse))
~> set edit:prompt = {
tilde-abbr $pwd
styled '❱ ' bright-red
}
~❱ # Fancy unicode prompts! elf✸host.example.com
The tilde-abbr
command abbreviates home
directory to a tilde. The constantly
command
returns a function that always writes the same value(s) to the value output. The
styled
command writes styled output.
4.5.3. Changing PATH
Another common task in the interactive startup script is to set the search path.
You can do set the environment variable directly (all environment variables have
a E:
prefix):
set E:PATH = /opts/bin:/bin:/usr/bin
But it is usually nicer to set the $paths
instead:
set paths = [/opts/bin /bin /usr/bin]
5. Shell scripting commands
Elvish has its own set of builtin commands. This section helps you find commands that correspond to commands in traditional shells.
5.1. command
To force Elvish to treat a command as an external command, prefix it with
e:
.
5.2. export
In Elvish, environment variables live in the
E:
namespace. There is no concept
of exporting a variable to the environment; environment variables are always
“exported” to child processes, and non-environment variables never are.
5.3. source
To build reusable libraries, use Elvish’s module mechanism.
To execute a dynamic piece of code for side effect, use
eval
. If the code lives in a file, write
eval (slurp < /path/to/file)
.
Due to Elvish’s scoping rules, files executed using either of the mechanism
above can’t create new variables in the current namespace. For example,
eval 'var foo = bar'; echo $foo
won’t work. However, the REPL’s namespace
can be manipulated with edit:add-var
.
5.4. test
To test files, use commands in the path module.
To compare numbers, use number comparison commands like
<
.
To compare strings, use string comparison commands like
<s
.
To perform boolean operations, use
and
,
or
or
not
. Note: and
and or
are part of the
language rather than the builtin module, since they perform
short-circuit evaluation
and don’t always evaluate all the arguments.
5.5. which
To check if an external command exists, use has-external.
To query the path of an external command, use search-external.