Table of Content:
- 1 Introduction
- 2 Source code encoding
- 3 Lexical elements
- 4 Value types
- 5 Variable
- 6 Expressions
- 7 Command forms
- 8 Special
commands
- 8.1
Declaring variables:
var
- 8.2
Assigning variables or elements:
set
- 8.3
Temporarily assigning variables or elements:
tmp
- 8.4
Deleting variables or elements:
del
- 8.5 Logics:
and
,or
,coalesce
- 8.6
Condition:
if
- 8.7
Conditional loop:
while
- 8.8
Iterative loop:
for
- 8.9
Exception control:
try
- 8.10
Function definition:
fn
- 8.11
Language pragmas:
pragma
- 8.1
Declaring variables:
- 9 Pipeline
- 10 Code Chunk
- 11 Exception and Flow Commands
- 12 Namespaces and Modules
1 Introduction
This document describes the Elvish programming language. It is both a specification and an advanced tutorial. The parts of this document marked with either notes or called out as examples are non-normative, and only serve to help you understand the more formal descriptions.
Examples in this document might use constructs that have not yet been introduced, so some familiarity with the language is assumed. If you are new to Elvish, start with the learning materials.
2 Source code encoding
Elvish source code must be Unicode text encoded in UTF-8.
In this document, character is a synonym of Unicode codepoint or its UTF-8 encoding.
3 Lexical elements
3.1 Whitespace
In this document, an inline whitespace is any of the following:
A space (U+0020);
A tab (U+0009);
A comment: starting with
#
and ending before (but not including) the next carriage return, newline or end of file;A line continuation: a
^
followed by a newline ("\n"
), or a carriage return and newline ("\r\n"
).
A whitespace is any of the following:
An inline whitespace;
A carriage return (U+000D);
A newline (U+000A).
3.2 Metacharacters
The following metacharacters serve to introduce or delimit syntax constructs:
$
: introduces variable use*
and?
: forms wildcards(
and)
: encloses output captures{
and}
: encloses lambda literals or braced lists<
and>
: introduces IO redirections;
: separates pipelines in a code chunk|
: separates forms in a pipeline; encloses function signature&
: marks background pipelines; introduces key-value pairs in map literals, options, or function signatures
The following characters are parsed as metacharacters under certain conditions:
~
: introduces tilde expansion if appearing at the beginning of a compound expression=
: terminates map keys, option keys, or the variable name in temporary assignments
3.3 Single-quoted string
A single-quoted string consists of zero or more characters enclosed
in single quotes ('
). All enclosed characters represent
themselves, except the single quote.
Two consecutive single quotes are handled as a special case: they represent one single quote, instead of terminating a single-quoted string and starting another.
Examples: '*\'
evaluates to
*\
, and 'it''s'
evaluates to
it's
.
3.4 Double-quoted string
A double-quoted string consists of zero or more characters enclosed
in double quotes ("
). All enclosed characters represent
themselves, except backslashes (\
), which introduces
escape sequences. Double quotes are not allowed inside
double-quoted strings, except after backslashes.
The following escape sequences are supported (the “U+” notation represents Unicode codepoints in hexadecimal):
The following escape sequences represent some special characters:
\a
is U+0007 BEL (bell).\b
is U+0008 BS (backspace).\t
is U+0009 HT (horizontal tabulation).\n
is U+000A LF (line feed), the standard line termination character on Unix.\v
is U+000B VT (vertical tabulation).\f
is U+000C FF (form feed).\r
is U+000D CR (carriage return).\"
is U+0022, the double quote"
itself.\\
is U+005C, the backslash\
itself.
The following escape sequences encode any byte using their numeric values:
\
followed by exactly three octal digits.\x
followed by exactly two hexadecimal digits.
Examples: The character “A” (U+0041) is encoded using a single byte in UTF-8 (0x41), can be written as
\x41
or\101
. The character “ß” (U+00DF) is encoded using two bytes in UTF-8 (0xc3 and 0x9f), and can be written as\xc3\x9f
or\303\237
(not as\xdf
or\337
). These notations can be used to write arbitrary byte sequences that are not necessary valid UTF-8 sequences.Note:
\0
, while supported by C, is invalid in Elvish; write\000
instead.The following escape sequences encode any Unicode codepoint using their numeric values:
\u
followed by exactly four hexadecimal digits.\U
followed by exactly eight hexadecimal digits.
Examples: The character “A” (U+0041) can be written as
\u0041
or\U00000041
. The character “ß” (U+00DF) can be written as\u00df
or\U000000df
.The following escape sequences encode ASCII control characters with the traditional caret notation:
\^
followed by a single character between U+0040 and U+005F represents the codepoint that is 0x40 lower than it. For example,\^I
is the tab character: 0x49 (I
) - 0x40 = 0x09 (TAB).\^?
represents DEL (U+007F).\c
followed by character X is equivalent to\^
followed by X.
An unsupported escape sequence results in a parse error.
Note: Unlike most other shells, double-quoted
strings in Elvish do not support interpolation. For instance,
"$name"
simply evaluates to a string containing
$name
. To get a similar effect, simply concatenate strings:
instead of "my name is $name"
, write
"my name is "$name
. Under the hood this is a compounding operation.
3.5 Bareword
A string can be written without quoting – a bareword, if it only includes the characters from the following set:
ASCII letters (a-z and A-Z) and numbers (0-9);
The symbols
!%+,-./:@\_
;Non-ASCII codepoints that are printable, as defined by unicode.IsPrint in Go’s standard library.
Examples: a.txt
,
long-bareword
, elf@elv.sh
,
/usr/local/bin
, 你好世界
.
Moreover, ~
and =
are allowed to appear
without quoting when they are not parsed as metacharacters.
Note: since the backslash (\
) is a
valid bareword character in Elvish, it cannot be used to escape
metacharacter. Use quotes instead: for example, to echo a star, write
echo "*"
or echo '*'
, not
echo \*
. The last command will try to output filenames
starting with \
.
4 Value types
4.1 String
A string is a (possibly empty) sequence of bytes.
Single-quoted string literals, double-quoted string literals and barewords all evaluate to string values. Unless
otherwise noted, different syntaxes of string literals are equivalent in
the code. For instance, xyz
, 'xyz'
and
"xyz"
are different syntaxes for the same string with
content xyz
.
Strings that contain UTF-8 encoded text can be indexed with a byte index where a codepoint starts, which results in the codepoint that starts there. The index can be given as either a typed number, or a string that parses to a number. Examples:
In the string
elv
, every codepoint is encoded with only one byte, so 0, 1, 2 are all valid indices:~> put elv[0]
▶ e
~> put elv[1]
▶ l
~> put elv[2]
▶ vIn the string
世界
, each codepoint is encoded with three bytes. The first codepoint occupies byte 0 through 2, and the second occupies byte 3 through 5. Hence valid indices are 0 and 3:~> put 世界[0]
▶ 世
~> put 世界[3]
▶ 界
Such strings may also be indexed with a slice (see documentation of list for slice syntax). The range determined by the slice is also interpreted as byte indices, and the range must begin and end at codepoint boundaries.
The behavior of indexing a string that does not contain valid UTF-8-encoded Unicode text is unspecified.
Note: String indexing will likely change.
4.2 Number
Elvish supports several types of numbers. There is no literal syntax,
but they can be constructed by passing their string
representation to the num
builtin command:
Integers are written in decimal (e.g.
10
), hexadecimal (e.g.0xA
), octal (e.g.0o12
) or binary (e.g.0b1010
).NOTE: Integers with leading zeros are now parsed as octal (e.g.
010
is the same as0o10
, or8
), but this is subject to change (#1372).Rationals are written as two exact integers joined by
/
, e.g.1/2
or0x10/100
(16/100).Floating-point numbers are written with a decimal point (e.g.
10.0
) or using scientific notation (e.g.1e1
or1.0e1
). There are also three additional special floating-point values:+Inf
,-Inf
andNaN
.
Digits may be separated by underscores, which are ignored; this
permits separating the digits into groups to improve readability. For
example, 1000000
and 1_000_000
are equivalent,
so are 1.234_56e3
and 1.23456e3
, or
1_2_3
and 123
.
The string representation is case-insensitive.
4.2.1 Strings and numbers
Strings and numbers are distinct types; for example, 2
and (num 2)
are distinct values.
However, by convention, all language constructs that expect numbers (e.g. list indices) also accept strings that can be converted to numbers. This means that most of the time, you can just use the string representation of numbers, instead of explicitly constructing number values. Builtin numeric commands follow the same convention.
When the word number appears unqualified in other sections of this document, it means either an explicitly number-typed value (typed number), or its string representation.
When a typed number is converted to a string (e.g. with
to-string
), the result is guaranteed to convert back to the
original number. In other words, eq $x (num (to-string $x))
always outputs $true
if $x
is a typed
number.
4.2.2 Exactness
Integers and rationals are exact numbers; their precision is only limited by the available memory, and many (but not all) operations on them are guaranteed to produce mathematically correct results.
Floating-point numbers are IEEE 754 double-precision. Since operations on floating-point numbers in general are not guaranteed to be precise, they are always considered inexact.
This distinction is important for some builtin commands; see exactness-preserving commands.
4.3 List
A list is a value containing a sequence of values. Values in a list are called its elements. Each element has an index, starting from zero.
List literals are surrounded by square brackets [ ]
,
with elements separated by whitespace. Examples:
~> put [lorem ipsum]
▶ [lorem ipsum]
~> put [lorem
ipsum
foo
bar]
▶ [lorem ipsum foo bar]
Note: In Elvish, commas have no special meanings and are valid bareword characters, so don’t use them to separate elements:
~> var li = [a, b]
~> put $li
▶ [a, b]
~> put $li[0]
▶ a,
A list can be indexed with the index of an element to obtain the element, which can take one of two forms:
A non-negative integer, an offset counting from the beginning of the list. For example,
$li[0]
is the first element of$li
.A negative integer, an offset counting from the back of the list. For instance,
$li[-1]
is the last element$li
.
In both cases, the index can be given either as a typed number or a number-like string.
A list can also be indexed with a slice to obtain a sublist, which can take one of two forms:
A slice
$a..$b
, where both$a
and$b
are integers. The result is sublist of$li[$a]
up to, but not including,$li[$b]
. For instance,$li[4..7]
equals[$li[4] $li[5] $li[6]]
, while$li[1..-1]
contains all elements from$li
except the first and last one.Both integers may be omitted;
$a
defaults to 0 while$b
defaults to the length of the list. For instance,$li[..2]
is equivalent to$li[0..2]
,$li[2..]
is equivalent to$li[2..(count $li)]
, and$li[..]
makes a copy of$li
. The last form is rarely useful, as lists are immutable.Note that the slice needs to be a single string, so there cannot be any spaces within the slice. For instance,
$li[2..10]
cannot be written as$li[2.. 10]
; the latter contains two indices and is equivalent to$li[2..] $li[10]
(see Indexing).A slice
$a..=$b
, which is similar to$a..$b
, but includes$li[$b]
.
Examples:
~> var li = [lorem ipsum foo bar]
~> put $li[0]
▶ lorem
~> put $li[-1]
▶ bar
~> put $li[0..2]
▶ [lorem ipsum]
4.4 Map
A map is a value containing unordered key-value pairs.
Map literals are surrounded by square brackets; a key/value pair is
written &key=value
(reminiscent to HTTP query
parameters), and pairs are separated by whitespaces. Whitespaces are
allowed after =
, but not before =
.
Examples:
~> put [&foo=bar &lorem=ipsum]
▶ [&foo=bar &lorem=ipsum]
~> put [&a= 10
&b= 23
&sum= (+ 10 23)]
▶ [&a=10 &b=23 &sum=33]
The literal of an empty map is [&]
.
Specifying a key without =
or a value following it is
equivalent to specifying $true
as the value. Specifying a
key with =
but no value following it is equivalent to
specifying the empty string as the value. Example:
~> echo [&a &b=]
[&a=$true &b='']
A map can be indexed by any of its keys. Unlike strings and lists,
there is no support for slices, and ..
and ..=
have no special meanings. Examples:
~> var map = [&a=lorem &b=ipsum &a..b=haha]
~> echo $map[a]
lorem
~> echo $map[a..b]
haha
You can test if a key is present using has-key
and enumerate the
keys using the keys
builtins.
Note: Since &
is a metacharacter, key-value pairs do not have to
follow whitespaces; [&a=lorem&b=ipsum]
is
equivalent to [&a=lorem &b=ipsum]
, just less
readable. This might change in future.
4.5 Pseudo-map
A pseudo-map is not a single concrete data type. It refers to concrete types that behave like maps with some restrictions.
A pseudo-map has a fixed set of keys whose values can be accessed by
indexing like you would for a regular map. Similarly, you can use commands like keys
and has-key
on such objects.
Unlike a normal map, it is currently not possible to create a modified version of an existing pseudo-map: it is not possible to create a pseudo-map with new keys, without existing keys, or with a different value for a given key.
The pseudo-map mechanism is often used for introspection. For
example, exceptions, user-defined functions, and $buildinfo
are
pseudo-maps.
4.6 Nil
The value $nil
serves as the initial value of variables
that are declared but not assigned.
4.7 Boolean
There are two boolean values, $true
and
$false
.
When converting non-boolean values to the boolean type,
$nil
and exceptions convert to $false
; such
values and $false
itself are booleanly
false. All the other non-boolean values convert to
$true
; such values and $true
itself are
booleanly true.
4.8 Exception
An exception carries information about errors during the execution of code.
There is no literal syntax for exceptions. See the discussion of exception and flow commands for more information about this data type.
An exception is a pseudo-map with a
reason
field, which is in turn a pseudo-map. The reason
pseudo-map has has a type
field identifying how the
exception was raised, and further fields depending on the type:
If the
type
field isfail
, the exception was raised by the fail command.In this case, the
content
field contains the argument tofail
.If the
type
field isflow
, the exception was raised by one of the flow commands.In this case, the
name
field contains the name of the flow command.If the
type
field ispipeline
, the exception was a result of multiple commands in the same pipeline raising exceptions.In this case, the
exceptions
field contains the exceptions from the individual commands.If the
type
field starts withexternal-cmd/
, the exception was caused by one of several conditions of an external command. In this case, the following fields are available:The
cmd-name
field contains the name of the command.The
pid
field contains the PID of the command.
If the
type
field isexternal-cmd/exited
, the external command exited with a non-zero status code. In this case, theexit-status
field contains the exit status.If the
type
field isexternal-cmd/signaled
, the external command was killed by a signal. In this case, the following extra fields are available:The
signal-name
field contains the name of the signal.The
signal-number
field contains the numerical value of the signal, as a string.The
core-dumped
field is a boolean reflecting whether a core dump was generated.
If the
type
field isexternal-cmd/stopped
, the external command was stopped. In this case, the following extra fields are available:The
signal-name
field contains the name of the signal.The
signal-number
field contains the numerical value of the signal, as a string.The
trap-cause
field contains the number indicating the trap cause.
Examples:
~> put ?(fail foo)[reason]
▶ [&content=foo &type=fail]
~> put ?(return)[reason]
▶ [&name=return &type=flow]
~> put ?(false)[reason]
▶ [&cmd-name=false &exit-status=1 &pid=953421 &type=external-cmd/exited]
4.9 File
There is no literal syntax for the file type. This type is returned by commands such as file:open and path:temp-file. It can be used as the target of a redirection rather than a filename.
A file object is a pseudo-map with fields
fd
(an int) and name
(a string). If the file
is closed the fd will be -1.
4.10 Function
A function encapsulates a piece of code that can be executed in an ordinary command, and takes its arguments and options. Functions are first-class values; they can be kept in variables, used as arguments, output on the value channel and embedded in other data structures. Elvish comes with a set of builtin functions, and Elvish code can also create user-defined functions.
Note: Unlike most programming languages, functions in Elvish do not have return values. Instead, they can output values, which can be captured later.
A function literal, or alternatively a lambda, evaluates to a user-defined function. The literal syntax consists of an optional signature list, followed by a code chunk that defines the body of the function.
Here is an example without a signature:
~> var f = { echo "Inside a lambda" }
~> put $f
▶ <closure 0x18a1a340>
One or more whitespace characters after {
is required:
Elvish relies on the presence of whitespace to disambiguate function
literals and braced lists.
Note: It is good style to put some whitespace before
the closing }
for symmetry, but this is not required by the
syntax.
Functions defined without a signature list do not accept any arguments or options. To do so, write a signature list. Here is an example:
~> var f = {|a b| put $b $a }
~> $f lorem ipsum
▶ ipsum
▶ lorem
Like in the left hand of assignments, if you prefix one of the
arguments with @
, it becomes a rest
argument, and its value is a list containing all the remaining
arguments:
~> var f = {|a @rest| put $a $rest }
~> $f lorem
▶ lorem
▶ []
~> $f lorem ipsum dolar sit
▶ lorem
▶ [ipsum dolar sit]
~> set f = {|a @rest b| put $a $rest $b }
~> $f lorem ipsum dolar sit
▶ lorem
▶ [ipsum dolar]
▶ sit
You can also declare options in the signature. The syntax is
&name=default
(like a map pair), where
default
is the default value for the option; the value of
the option will be kept in a variable called name
:
~> var f = {|&opt=default| echo "Value of $opt is "$opt }
~> $f
Value of $opt is default
~> $f &opt=foobar
Value of $opt is foobar
Options must have default values: Options should be optional.
If you call a function with too few arguments, too many arguments or unknown options, an exception is thrown:
~> {|a| echo $a } foo bar
Exception: need 1 arguments, got 2
[tty], line 1: {|a| echo $a } foo bar
~> {|a b| echo $a $b } foo
Exception: need 2 arguments, got 1
[tty], line 1: {|a b| echo $a $b } foo
~> {|a b @rest| echo $a $b $rest } foo
Exception: need 2 or more arguments, got 1
[tty], line 1: {|a b @rest| echo $a $b $rest } foo
~> {|&k=v| echo $k } &k2=v2
Exception: unknown option k2
[tty], line 1: {|&k=v| echo $k } &k2=v2
A user-defined function is a pseudo-map. If
$f
is a user-defined function, it has the following
fields:
$f[arg-names]
is a list containing the names of the arguments.$f[rest-arg]
is the index of the rest argument. If there is no rest argument, it is-1
.$f[opt-names]
is a list containing the names of the options.$f[opt-defaults]
is a list containing the default values of the options, in the same order as$f[opt-names]
.$f[def]
is a string containing the definition of the function, including the signature and the body.$f[body]
is a string containing the body of the function, without the enclosing brackets.$f[src]
is a map-like data structure containing information about the source code that the function is defined in. It contains the same value that the src function would output if called from the function.
5 Variable
A variable is a named storage location for holding a value. The following characters can be used in variable names without quoting:
ASCII letters (a-z and A-Z) and numbers (0-9);
The symbols
-_:~
;Non-ASCII codepoints that are printable, as defined by unicode.IsPrint in Go’s standard library.
A variable exist after it is declared using var
, and its value may be mutated by
further assignments. It can be used as an
expression or part of an expression.
Note: In most other shells, variables can map
directly to environmental variables: $PATH
is the same as
the PATH
environment variable. This is not the case in
Elvish. Instead, environment variables are put in a dedicated
E:
namespace; the environment variable PATH
is
known as $E:PATH
. The $PATH
variable, on the
other hand, does not exist initially, and if you have defined it, only
lives in a certain lexical scope within the Elvish interpreter.
You will notice that variables sometimes have a leading dollar
$
, and sometimes not. The tradition is that they do when
they are used for their values, and do not otherwise (e.g. in
assignment). This is consistent with most other shells.
5.1 Variable suffix
There are two characters that have special meanings and extra type constraints when used as the suffix of a variable name:
If a variable name ends with
~
, it can only take callable values, which are functions and external commands. Such variables are consulted when resolving ordinary commands. The default value is the builtinnop
command.If a variable name ends with
:
, it can only take namespaces as values. They are used for accessing namespaced variables.
5.2 Scoping rule
Elvish has lexical scoping. A file or an interactive prompt starts with a top-level scope, and a function literal introduce new lexical scopes.
When you use a variable, Elvish looks for it in the current lexical scope, then its parent lexical scope and so forth, until the outermost scope:
~> var x = 12
~> { echo $x } # $x is in the outer scope
12
~> { y = bar; { echo $y } } # $y is in the outer scope
bar
If a variable is not in any of the lexical scopes, Elvish tries to resolve it in the builtin namespace, and if that also fails, fails with an error:
~> echo $pid # builtin
36613
~> echo $nonexistent
Compilation error: variable $nonexistent not found
[interactive], line 1:
echo $nonexistent
Note that Elvish resolves all variables in a code chunk before starting to execute any of it; that is why the error message above says compilation error. This can be more clearly observed in the following example:
~> echo pre-error; echo $nonexistent
Compilation error: variable $nonexistent not found
[tty], line 1: echo pre-error; echo $nonexistent
5.3 Closure semantics
When a function literal refers to a variable in an outer scope, the function will keep that variable alive, even if that variable is the local variable of an outer function that that function has returned. This is called closure semantics, because the function literal “closes” over the environment it is defined in.
In the following example, the make-adder
function
outputs two functions, both referring to a local variable
$n
. Closure semantics means that:
Both functions can continue to refer to the
$n
variable aftermake-adder
has returned.Multiple calls to the
make-adder
function generates distinct instances of the$n
variables.
~> fn make-adder {
var n = 0
put { put $n } { set n = (+ $n 1) }
}
~> var getter adder = (make-adder)
~> $getter # $getter outputs $n
▶ 0
~> $adder # $adder increments $n
~> $getter # $getter and $setter refer to the same $n
▶ 1
~> var getter2 adder2 = (make-adder)
~> $getter2 # $getter2 and $getter refer to different $n
▶ 0
~> $getter
▶ 1
Variables that get “captured” in closures are called
upvalues;. When capturing upvalues, Elvish only
captures the variables that are used. In the following example,
$m
is not an upvalue of $g
because it is not
used:
~> fn f { var m = 2; var n = 3; put { put $n } }
~> var g = (f)
Note: This effect has impacts on the eval
command.
6 Expressions
Elvish has a few types of expressions. Some of those are new compared to most other languages, but some are very similar.
Unlike most other languages, expressions in Elvish may evaluate to any number of values. The concept of multiple values is distinct from a list of multiple elements.
6.1 Literal
Literals of strings, lists, maps and functions all evaluate to one value of their corresponding types. They are described in their respective sections.
6.2 Variable use
A variable use expression is formed by a
$
followed by the name of the variable. Examples:
~> var foo = bar
~> var x y = 3 4
~> put $foo
▶ bar
~> put $x
▶ 3
If the variable name only contains the following characters (a subset
of bareword characters), the name can appear unquoted after
$
and the variable use expression extends to the longest
sequence of such characters:
ASCII letters (a-z and A-Z) and numbers (0-9);
The symbols
-_:~
. The colon:
is special; it is normally used for separating namespaces or denoting namespace variables;Non-ASCII codepoints that are printable, as defined by unicode.IsPrint in Go’s standard library.
Alternatively, $
may be followed immediately by a single-quoted
string or a double-quoted
string, in which cases the value of the string specifies the name of
the variable. Examples:
~> var "\n" = foo
~> put $"\n"
▶ foo
~> var '!!!' = bar
~> put $'!!!'
▶ bar
Unlike other shells and other dynamic languages, local namespaces in Elvish are statically checked. This means that referencing a nonexistent variable results in a compilation error, which is triggered before any code is actually evaluated:
~> echo $x
Compilation error: variable $x not found
[tty], line 1: echo $x
~> fn f { echo $x }
compilation error: variable $x not found
[tty 1], line 1: fn f { echo $x }
If a variable contains a list value, you can add @
before the variable name; this evaluates to all the elements within the
list. This is called exploding the variable:
~> var li = [lorem ipsum foo bar]
~> put $li
▶ [lorem ipsum foo bar]
~> put $@li
▶ lorem
▶ ipsum
▶ foo
▶ bar
Note: Since variable uses have higher precedence than indexing, this does not work for exploding a list that is an element of another list. For doing that, and exploding the result of other expressions (such as an output capture), use the builtin all command.)
6.3 Output capture
An output capture expression is formed by putting
parentheses ()
around a code
chunk. It redirects the output of the chunk into an internal pipe,
and evaluates to all the values that have been output.
~> + 1 10 100
▶ 111
~> var x = (+ 1 10 100)
~> put $x
▶ 111
~> put lorem ipsum
▶ lorem
▶ ipsum
~> var x y = (put lorem ipsum)
~> put $x
▶ lorem
~> put $y
▶ ipsum
If the chunk outputs bytes, Elvish strips the last newline (if any), and split them by newlines, and consider each line to be one string value:
~> put (echo "a\nb")
▶ a
▶ b
Trailing carriage returns are also stripped from each line, which
effectively makes \r\n
also valid line separators:
~> put (echo "a\r\nb")
▶ a
▶ b
Note 1. Only the last newline is ever removed, so
empty lines are preserved; (echo "a\n")
evaluates to two
values, "a"
and ""
.
Note 2. One consequence of this mechanism is that
you can not distinguish outputs that lack a trailing newline from
outputs that have one; (echo what)
evaluates to the same
value as (print what)
. If such a distinction is needed, use
slurp
to preserve the
original bytes output.
If the chunk outputs both values and bytes, the values of output capture will contain both value outputs and lines. However, the ordering between value output and byte output might not agree with the order in which they happened:
~> put (put a; echo b) # value order need not be the same as output order
▶ b
▶ a
Note: If you want to capture the stdout and stderr byte streams independent of each other, see the example in the run-parallel documentation.
6.4 Exception capture
An exception capture expression is formed by putting
?()
around a code chunk. It runs the chunk and evaluates to
the exception it throws.
~> fail bad
Exception: bad
Traceback:
[interactive], line 1:
fail bad
~> put ?(fail bad)
▶ ?(fail bad)
If there was no error, it evaluates to the special value
$ok
:
~> nop
~> put ?(nop)
▶ $ok
Exceptions are booleanly false and $ok
is booleanly
true. This is useful in if
(introduced later):
if ?(test -d ./a) {
# ./a is a directory
}
Note: Exception captures do not affect the output of the code chunk. You can combine output capture and exception capture:
var output = (var error = ?(commands-that-may-fail))
6.5 Braced list
A braced list consists of multiple expressions
separated by whitespaces and surrounded by braces ({}
).
There must be no space after the opening brace. A braced list evaluates
to whatever the expressions inside it evaluate to. Its most typical use
is grouping multiple values in a compound
expression. Example:
~> put {a b}-{1 2}
▶ a-1
▶ a-2
▶ b-1
▶ b-2
It can also be used to affect the order of evaluation. Examples:
~> put *
▶ foo
▶ bar
~> put *o
▶ foo
~> put {*}o
▶ fooo
▶ baro
Note: When used to affect the order of evaluation, braced lists are very similar to parentheses in C-like languages.
Note: A braced list is an expression. It is a syntactical construct and not a separate data structure.
Elvish currently also supports using commas to separate items in a braced list. This will likely be removed in future, but it also means that literal commas must be quoted right now.
6.6 Indexing
An indexing expression is formed by appending one or
more indices inside a pair of brackets ([]
) after another
expression (the indexee). Examples:
~> var li = [foo bar]
~> put $li[0]
▶ foo
~> var li = [[foo bar] quux]
~> put $li[0][0]
▶ foo
~> put [[foo bar]][0][0]
▶ foo
If the expression being indexed evaluates to multiple values, the indexing operation is applied on each value. Example:
~> put (put [foo bar] [lorem ipsum])[0]
▶ foo
▶ lorem
~> put {[foo bar] [lorem ipsum]}[0]
▶ foo
▶ lorem
If there are multiple index expressions, or the index expression evaluates to multiple values, the indexee is indexed once for each of the index value. Examples:
~> put elv[0 2 0..2]
▶ e
▶ v
▶ el
~> put [lorem ipsum foo bar][0 2 0..2]
▶ lorem
▶ foo
▶ [lorem ipsum]
~> put [&a=lorem &b=ipsum &a..b=haha][a a..b]
▶ lorem
▶ haha
If both the indexee and index evaluate to multiple values, the results generated from the first indexee appear first. Example:
~> put {[foo bar] [lorem ipsum]}[0 1]
▶ foo
▶ bar
▶ lorem
▶ ipsum
6.7 Compounding
A compound expression is formed by writing several expressions together with no space in between. A compound expression evaluates to a string concatenation of all the constituent expressions. Examples:
~> put 'a'b"c" # compounding three string literals
▶ abc
~> var v = value
~> put '$v is '$v # compounding one string literal with one string variable
▶ '$v is value'
When one or more of the constituent expressions evaluate to multiple values, the result is all possible combinations:
~> var li = [foo bar]
~> put {a b}-$li[0 1]
▶ a-foo
▶ a-bar
▶ b-foo
▶ b-bar
The order of the combinations is determined by first taking the first value in the leftmost expression that generates multiple values, and then taking the second value, and so on.
6.8 Tilde expansion
An unquoted tilde at the beginning of a compound expression triggers
tilde expansion. The remainder of this expression must
be a string. The part from the beginning of the string up to the first
/
(or the end of the word if the string does not contain
/
), is taken as a user name; and they together evaluate to
the home directory of that user. If the user name is empty, the current
user is assumed.
In the following example, the home directory of the current user is
/home/xiaq
, while that of the root user is
/root
:
~> put ~
▶ /home/xiaq
~> put ~root
▶ /root
~> put ~/xxx
▶ /home/xiaq/xxx
~> put ~root/xxx
▶ /root/xxx
Note that tildes are not special when they appear elsewhere in a word:
~> put a~root
▶ a~root
If you need them to be, use a braced list:
~> put a{~root}
▶ a/root
6.9 Wildcard expansion
Wildcard patterns are expressions that contain wildcards. Wildcard patterns evaluate to all filenames they match.
In examples in this section, we will assume that the current directory has the following structure:
.x.conf
a.cc
ax.conf
foo.cc
d/
|__ .x.conf
|__ ax.conf
|__ y.cc
.d2/
|__ .x.conf
|__ ax.conf
Elvish supports the following wildcards:
?
matches one arbitrary character except/
. For example,?.cc
matchesa.cc
;*
matches any number of arbitrary characters except/
. For example,*.cc
matchesa.cc
andfoo.cc
;**
matches any number of arbitrary characters including/
. For example,**.cc
matchesa.cc
,foo.cc
andb/y.cc
.
The following behaviors are default, although they can be altered by modifiers:
When the entire wildcard pattern has no match, an error is thrown.
None of the wildcards matches
.
at the beginning of filenames. For example:?x.conf
does not match.x.conf
;d/*.conf
does not matchd/.x.conf
;**.conf
does not matchd/.x.conf
.
Wildcards can be modified using the same syntax as
indexing. For instance, in *[match-hidden]
the
*
wildcard is modified with the match-hidden
modifier. Multiple matchers can be chained like
*[set:abc][range:0-9]
. In which case they are OR’ed
together.
There are two kinds of modifiers:
Global modifiers apply to the whole pattern and can be placed after any wildcard:
nomatch-ok
tells Elvish not to throw an error when there is no match for the pattern. For instance, in the example directoryput bad*
will be an error, butput bad*[nomatch-ok]
does exactly nothing.but:xxx
(wherexxx
is any filename) excludes the filename from the final result.type:xxx
(wherexxx
is a recognized file type from the list below). Only one type modifier is allowed. For example, to find the directories at any level below the current working directory:**[type:dir]
.dir
will match if the path is a directory.regular
will match if the path is a regular file.
Although global modifiers affect the entire wildcard pattern, you can
add it after any wildcard, and the effect is the same. For example,
put */*[nomatch-ok].cpp
and
put *[nomatch-ok]/*.cpp
do the same thing. On the other
hand, you must add it after a wildcard, instead of after the entire
pattern: put */*.cpp[nomatch-ok]
unfortunately does not do
the correct thing. (This will probably be fixed.)
Local modifiers only apply to the wildcard it immediately follows:
match-hidden
tells the wildcard to match.
at the beginning of filenames, e.g.*[match-hidden].conf
matches.x.conf
andax.conf
.Being a local modifier, it only applies to the wildcard it immediately follows. For instance,
*[match-hidden]/*.conf
matchesd/ax.conf
and.d2/ax.conf
, but notd/.x.conf
or.d2/.x.conf
.Character matchers restrict the characters to match:
Character sets, like
set:aeoiu
;Character ranges like
range:a-z
(includingz
) orrange:a~z
(excludingz
);Character classes:
control
,digit
,graphic
,letter
,lower
,mark
,number
,print
,punct
,space
,symbol
,title
, andupper
. See the Is* functions here for their definitions.
Note the following caveats:
Local matchers chained together in separate modifiers are OR’ed. For instance,
?[set:aeoiu][digit]
matches all files with the charsaeoiu
or containing a digit.Local matchers combined in the same modifier, such as
?[set:aeoiu digit]
, behave in a hard to explain manner. Do not use this form as the behavior is likely to change in the future.Dots at the beginning of filenames always require an explicit
match-hidden
, even if the matcher includes.
. For example,?[set:.a]x.conf
does not match.x.conf
; you have to?[set:.a match-hidden]x.conf
.Likewise, you always need to use
**
to match slashes, even if the matcher includes/
. For example*[set:abc/]
is the same as*[set:abc]
.
6.10 Order of evaluation
An expression can use a combination of indexing, tilde expansion, wildcard and compounding. The order of evaluation is as follows:
Literals, variable uses, output captures and exception captures and braced lists have the highest precedence and are evaluated first.
Indexing has the next highest precedence and is then evaluated first.
Expression compounding then happens. Tildes and wildcards are kept unevaluated.
If the expression starts with a tilde, tilde expansion happens. If the tilde is followed by a wildcard, an exception is raised.
If the expression contains any wildcard, wildcard expansion happens.
Here an example: in ~/$li[0 1]/*
(where $li
is a list [foo bar]
), the expression is evaluated as
follows:
The variable use
$li
evaluates to the list[foo bar]
.The indexing expression
$li[0]
evaluates to two stringsfoo
andbar
.Compounding the expression, the result is
~/foo/*
and~/bar/*
.Tilde expansion happens; assuming that the user’s home directory is
/home/elf
, the values are now/home/elf/foo/*
and/home/elf/bar/*
.Wildcard expansion happens, evaluating the expression to all the filenames within
/home/elf/foo
and/home/elf/bar
. If any directory is empty or nonexistent, an exception is thrown.
To force a particular order of evaluation, group expressions using a braced list.
7 Command forms
A command form is either an ordinary command or a special command. Both types have access to IO ports, which can be modified via redirections.
When Elvish parses a command form, it applies the following process to decide its type:
If the first expression in the command form contains a single string literal, and the string value matches one of the special commands, it is a special command.
Otherwise, it is an ordinary command.
7.1 Ordinary command
An ordinary command form consists of a command head, and any number of arguments and options.
The first expression in an ordinary command is the command head. If the head is a single string literal, it is subject to static resolution:
If a variable with name
head~
(wherehead
is the value of the head) exists, the head will evaluate as if it is$head~
; i.e., a function invocation.If the head contains at least one slash, it is treated as an external command with the value as its path relative to the current directory.
Otherwise, the head is considered “unknown”, and the behavior is controlled by the
unknown-command
pragma:If the
unknown-command
pragma is set toexternal
(the default), the head is treated as the name of an external command, to be searched in the$E:PATH
during runtime.If the
unknown-command
pragma is set todisallow
, such command heads trigger a compilation error.
If the head is not a single string literal, it is evaluated as a normal expression. The expression must evaluate to one value, and the value must be one of the following:
A callable value: a function or external command.
A string containing at least one slash, in which case it is treated like an external command with the string value as its path.
Examples of commands using static resolution:
~> put x # resolves to builtin function $put~
▶ x
~> var f~ = { put 'this is f' }
~> f # resolves to user-defined function $f~
▶ 'this is f'
~> whoami # resolves to external command whoami
elf
Examples of commands using a dynamic callable head:
~> $put~ x
▶ x
~> (external whoami)
elf
~> { put 'this is a lambda' }
▶ 'this is a lambda'
Note: The last command resembles a code block in C-like languages in syntax, but is quite different under the hood: it works by defining a function on the fly and calling it immediately.
Examples of commands using a dynamic string head:
~> var x = /bin/whoami
~> $x
elf
~> set x = whoami
~> $x # dynamic strings can only used when containing slash
Exception: bad value: command must be callable or string containing slash, but is string
[tty 10], line 1: $x
The definition of barewords is relaxed when parsing the head, and
includes <
, >
, and *
. These
are all names of numeric builtins:
~> < 3 5 # less-than
▶ $true
~> > 3 5 # greater-than
▶ $false
~> * 3 5 # multiplication
▶ 15
Arguments and options can be supplied to commands. Arguments are arbitrary words, while options have exactly the same syntax as key-value pairs in map literals. They are separated by inline whitespaces and may be intermixed:
~> echo &sep=, a b c # &seq=, is an option; a b c are arguments
a,b,c
~> echo a b &sep=, c # same, with the option mixed within arguments
a,b,c
Note: Since options have the same syntax as
key-value pairs in maps, &key
is equivalent to
&key=$true
:
~> fn f {|&opt=$false| put $opt }
~> f &opt
▶ $true
Note: Since &
is a metacharacter, it can be used to start an
option immediately after the command name;
echo&sep=, a b
is equivalent to
echo &sep=, a b
, just less readable. This might change
in future.
7.2 Special command
A special command form has the same syntax with an ordinary command, but how it is executed depends on the command head. See special commands.
7.3 Temporary assignment
Note: Starting from 0.18.0, this syntax will be
deprecated in favor of the tmp
special
command.
You can prepend any command form with temporary assignments, which gives variables temporarily values during the execution of that command.
In the following example, $x
and $y
are
temporarily assigned 100 and 200:
~> var x y = 1 2
~> x=100 y=200 + $x $y
▶ 300
~> echo $x $y
1 2
In contrary to normal assignments, there should be no whitespaces
around the equal sign =
. To have multiple variables in the
left-hand side, use braces:
~> var x y = 1 2
~> fn f { put 100 200 }
~> {x,y}=(f) + $x $y
▶ 300
If you use a previously undefined variable in a temporary assignment, its value will become the empty string after the command finishes.
Since var
and set
are also commands, they
can also be prepended with temporary assignments:
~> var x = 1
~> x=100 var y = (+ 133 $x)
~> put $x $y
▶ 1
▶ 233
Temporary assignments must all appear at the beginning of the command
form. As soon as something that is not a temporary assignments is
parsed, Elvish no longer parses temporary assignments. For instance, in
x=1 echo x=1
, the second x=1
is not a
temporary assignment, but a bareword.
Note: Elvish’s behavior differs from bash (or zsh) in one important place. In bash, temporary assignments to variables do not affect their direct appearance in the command:
bash-4.4$ x=1
bash-4.4$ x=100 echo $x
1
7.4 IO ports
A command have access to a number of IO ports. Each IO port is identified by a number starting from 0, and combines a traditional file object, which conveys bytes, and a value channel, which conveys values.
Elvish starts with 3 IO ports at the top level with special significance for commands:
Port 0, known as standard input or stdin, and is used as the default input port by builtin commands.
Port 1, known as standard output or stdout, and is used as the default output port by builtin commands.
Port 2, known as standard error or stderr, is currently not special for builtin commands, but usually has special significance for external commands.
Value channels are typically created by a pipeline, and used to pass values between commands in the same pipeline. At the top level, they are initialized with special values:
The value channel for port 0 never produces any values when read.
The value channels for port 1 and 2 are special channels that forward the values written to them to their file counterparts. Each value is put on a separate line, with a prefix controlled by
$value-out-indicator
. The default prefix is▶
followed by a space.
When running an external command, the file object from each port is used to create its file descriptor table. Value channels only work inside the Elvish process, and are not accessible to external commands.
IO ports can be modified with redirections or by pipelines.
7.5 Redirection
A redirection modifies the IO ports a command operate with. There are several variants.
A file redirection opens a file and associates it
with an IO port. The syntax consists of an optional destination IO port
(like 2
), a redirection operator (like >
)
and a filename (like error.log
):
The destination IO port determines which IO port to modify. It can be given either as the number of the IO port, or one of
stdin
,stdout
andstderr
, which are equivalent to 0, 1 and 2 respectively.The destination IO port can be omitted, in which case it is inferred from the redirection operator.
When the destination IO port is given, it must precede the redirection operator directly, without whitespaces in between; if there are whitespaces, otherwise Elvish will parse it as an argument instead.
The redirection operator determines the mode to open the file, and the destination IO port if it is not explicitly specified.
The filename names the file to open.
Possible redirection operators and their default FDs are:
<
for reading. The default IO port is 0 (stdin).>
for writing. The default IO port is 1 (stdout).>>
for appending. The default IO port is 1 (stdout).<>
for reading and writing. The default IO port is 1 (stdout).
Examples:
~> echo haha > log
~> cat log
haha
~> cat < log
haha
~> ls --bad-arg 2> error
Exception: ls exited with 2
Traceback:
[interactive], line 1:
ls --bad-arg 2> error
~> cat error
/bin/ls: unrecognized option '--bad-arg'
Try '/bin/ls --help' for more information.
IO ports modified by file redirections do not support value channels. To be more exact:
A file redirection using
<
sets the value channel to one that never produces any values.A file redirection using
>
,>>
or<>
sets the value channel to one that throws an exception when written to.
Examples:
~> put foo > file # will truncate file if it exists
Exception: port has no value output
[tty 2], line 1: put foo > file
~> echo content > file
~> only-values < file
~> # previous command produced nothing
Redirections can also be used for closing or duplicating IO ports.
Instead of writing a filename, use &fd
(where
fd
is a number, or any of stdin
,
stdout
and stderr
) for duplicating, or
&-
for closing. In this case, the redirection operator
only determines the default destination FD (and is totally irrevelant if
a destination IO port is specified). Examples:
~> date >&-
date: stdout: Bad file descriptor
Exception: date exited with 1
[tty 3], line 1: date >&-
~> put foo >&-
Exception: port has no value output
[tty 37], line 1: put foo >&-
If you have multiple related redirections, they are applied in the order they appear. For instance:
~> fn f { echo out; echo err >&2 } # echoes "out" on stdout, "err" on stderr
~> f >log 2>&1 # use file "log" for stdout, then use (changed) stdout for stderr
~> cat log
out
err
Redirections may appear anywhere in the command, except at the beginning, (this may be restricted in future). It’s usually good style to write redirections at the end of command forms.
8 Special commands
Special commands obey the same syntax rules as normal commands, but have evaluation rules that are custom to each command. Consider the following example:
~> or ?(echo x) ?(echo y) ?(echo z)
x
▶ $ok
In the example, the or
command first evaluates its first
argument, which has the value $ok
(a truish value) and the
side effect of outputting x
. Due to the custom evaluation
rule of or
, the rest of the arguments are not
evaluated.
If or
were a normal command, the code above is still
syntactically correct. However, Elvish would then evaluate all its
arguments, with the side effect of outputting x
,
y
and z
, before calling or
.
8.1 Declaring variables:
var
The var
special command declares local variables. It
takes any number of unqualified variable names (without the leading
$
). The variables will start out having value
$nil
. Examples:
~> var a
~> put $a
▶ $nil
~> var foo bar
~> put $foo $bar
▶ $nil
▶ $nil
To set alternative initial values, add an unquoted =
and
the initial values. Examples:
~> var a b = foo bar
~> put $a $b
▶ foo
▶ bar
Similar to set
, at most one of
variables may be prefixed with @
to function as a rest
variable.
When declaring a variable that already exists, the existing variable is shadowed. The shadowed variable may still be accessed indirectly if it is referenced by a function. Example:
~> var x = old
~> fn f { put $x }
~> var x = new
~> put $x
▶ new
~> f
▶ old
8.2 Assigning variables or
elements: set
The set
special command sets the value of variables or
elements.
It takes any number of lvalues (which refer to
either variables or elements), followed by an equal sign
(=
) and any number of expressions. The equal sign must
appear unquoted, as a single argument.
An lvalue is one of the following:
A variable name (without
$
).A variable name prefixed with
@
, for packing a variable number of values into a list and assigning to the variable.This variant is called a rest variable. There could be at most one rest variable.
Note: Schematically this is the reverse operation of exploding a variable when using it, which is why they share the
@
sign.A variable name followed by one or more indices in brackets (
[]
), for assigning to an element.
The number of values the expressions evaluate to and lvalues must be compatible. To be more exact:
If there is no rest variable, the number of values and lvalues must match exactly.
If there is a rest variable, the number of values should be at least the number of lvalues minus one.
All the variables to set must already exist; use the var
special command to declare new
variables.
Examples:
~> var x y z
~> set x = foo
~> put $x
▶ foo
~> set x y = lorem ipsum
~> put $x $y
▶ lorem
▶ ipsum
~> set x @y z = a b
~> put $x $y $z
▶ a
▶ []
▶ b
~> set x @y z = a b c d
~> put $x $y $z
▶ a
▶ [b c]
▶ d
~> set y[0] = foo
~> put $y
▶ [foo c]
If the variable name contains any character that may not appear unquoted in variable use expressions, it must be quoted even if it is otherwise a valid bareword:
~> var 'a/b'
~> set a/b = foo
compilation error: lvalue must be valid literal variable names
[tty 23], line 1: a/b = foo
~> set 'a/b' = foo
~> put $'a/b'
▶ foo
Lists and maps in Elvish are immutable. As a result, when assigning to the element of a variable that contains a list or map, Elvish does not mutate the underlying list or map. Instead, Elvish creates a new list or map with the mutation applied, and assigns it to the variable. Example:
~> var li = [foo bar]
~> var li2 = $li
~> set li[0] = lorem
~> put $li $li2
▶ [lorem bar]
▶ [foo bar]
8.3 Temporarily assigning variables
or elements: tmp
The tmp
command has the same syntax as set
, and also requires all variables to
already exist (use the var
special
command to declare new variables).
Unlike var
, it saves the values of all variables before
assigning them new values, and will restore them to the saved values
when the current function has finished.
The tmp
command can only be used inside a function.
Examples:
~> var x = foo
~> fn f { echo $x }
~> { tmp x = bar; f }
bar
~> f
foo
8.4 Deleting variables or elements:
del
The del
special command can be used to delete variables
or map elements. Operands should be specified without a leading dollar
sign, like the left-hand side of assignments.
Example of deleting variable:
~> var x = 2
~> echo $x
2
~> del x
~> echo $x
Compilation error: variable $x not found
[tty], line 1: echo $x
If the variable name contains any character that cannot appear
unquoted after $
, it must be quoted, even if it is
otherwise a valid bareword:
~> var 'a/b' = foo
~> del 'a/b'
Deleting a variable does not affect closures that have already captured it; it only removes the name. Example:
~> var x = value
~> fn f { put $x }
~> del x
~> f
▶ value
Example of deleting map element:
~> var m = [&k=v &k2=v2]
~> del m[k2]
~> put $m
▶ [&k=v]
~> var l = [[&k=v &k2=v2]]
~> del l[0][k2]
~> put $l
▶ [[&k=v]]
8.5 Logics: and
,
or
, coalesce
The and
special command outputs the first booleanly false value the arguments evaluate to, or
$true
when given no value. Examples:
~> and $true $false
▶ $false
~> and a b c
▶ c
~> and a $false
▶ $false
The or
special command outputs the first booleanly true value the arguments evaluate to, or
$false
when given no value. Examples:
~> or $true $false
▶ $true
~> or a b c
▶ a
~> or $false a b
▶ a
The coalesce
special command outputs the first non-nil value the arguments evaluate to, or
$nil
when given no value. Examples:
~> coalesce $nil a b
▶ a
~> coalesce $nil $nil
▶ $nil
~> coalesce $nil $nil a
▶ a
~> coalesce a b
▶ a
All three commands use short-circuit evaluation, and stop evaluating arguments as soon as it sees a value satisfying the termination condition. For example, none of the following throws an exception:
~> and $false (fail foo)
▶ $false
~> or $true (fail foo)
▶ $true
~> coalesce a (fail foo)
▶ a
8.6 Condition: if
TODO: Document the syntax notation, and add more examples.
Syntax:
if <condition> {
<body>
} elif <condition> {
<body>
} else {
<else-body>
}
The if
special command goes through the conditions one
by one: as soon as one evaluates to a booleanly true value, its
corresponding body is executed. If none of conditions are booleanly true
and an else body is supplied, it is executed.
The condition part is an expression, not a command like in other shells. Example:
fn tell-language {|fname|
if (has-suffix $fname .go) {
echo $fname" is a Go file!"
} elif (has-suffix $fname .c) {
echo $fname" is a C file!"
} else {
echo $fname" is a mysterious file!"
}
}
The condition part must be syntactically a single expression, but it can evaluate to multiple values, in which case they are and’ed:
if (put $true $false) {
echo "will not be executed"
}
If the expression evaluates to 0 values, it is considered true,
consistent with how and
works.
Tip: a combination of if
and ?()
gives you
a semantics close to other shells:
if ?(test -d .git) {
# do something
}
However, for Elvish’s builtin predicates that output values instead
of throw exceptions, the output capture construct ()
should
be used.
8.7 Conditional loop:
while
Syntax:
while <condition> {
<body>
} else {
<else-body>
}
Execute the body as long as the condition evaluates to a booleanly true value.
The else body, if present, is executed if the body has never been executed (i.e. the condition evaluates to a booleanly false value in the very beginning).
8.8 Iterative loop:
for
Syntax:
for <var> <container> {
<body>
} else {
<body>
}
Iterate the container (e.g. a list). In each iteration, assign the variable to an element of the container and execute the body.
The else body, if present, is executed if the body has never been executed (i.e. the iteration value has no elements).
8.9 Exception control:
try
(If you just want to capture the exception, you can use the more
concise exception capture construct ?()
instead.)
Syntax:
try {
<try-block>
} catch catch-varname {
<catch-block>
} else {
<else-block>
} finally {
<finally-block>
}
Only try
and try-block
are required. This
control structure behaves as follows:
The
try-block
is always executed first.If
catch
is present, any exception that occurs intry-block
is caught and stored incatch-varname
, andcatch-block
is then executed. Example:~> try { fail bad } catch e { put $e }
▶ ?(fail bad)If
catch
is not present, exceptions thrown fromtry
are not caught: for instance,try { fail bad } finally { echo foo }
will echofoo
, but the exception is not caught and will be propagated further.Note: this keyword is spelt
except
in Elvish 0.17.x and before, but is otherwise the same. Usingexcept
still works in Elvish 0.18.x but is deprecated; it will be removed in Elvish 0.19.0.Note: the word after
catch
names a variable, not a matching condition. Exception matching is not supported yet. For instance, you may want to only match exceptions that were created withfail bad
withexcept bad
, but in fact this creates a variable$bad
that contains whatever exception was thrown.If no exception occurs and
else
is present,else-block
is executed. Example:~> try { nop } else { echo well }
wellIf
finally-block
is present, it is executed. Examples:~> try { fail bad } finally { echo final }
final
Exception: bad
Traceback:
[tty], line 1:
try { fail bad } finally { echo final }
~> try { echo good } finally { echo final }
good
finalIf the exception was not caught (i.e.
catch
is not present), it is rethrown.
At least one of catch
and finally
must be
present, since a lone try { ... }
does not do anything on
its own, and is almost certainly a mistake. To swallow exceptions, an
explicit catch
clause must be given.
Exceptions thrown in blocks other than try-block
are not
caught. If an exception was thrown and either catch-block
or finally-block
throws another exception, the original
exception is lost. Examples:
~> try { fail bad } catch e { fail worse }
Exception: worse
Traceback:
[tty], line 1:
try { fail bad } catch e { fail worse }
~> try { fail bad } catch e { fail worse } finally { fail worst }
Exception: worst
Traceback:
[tty], line 1:
try { fail bad } catch e { fail worse } finally { fail worst }
8.10 Function definition:
fn
Syntax:
fn <name> <lambda>
Define a function with a given name. The function behaves in the same
way to the lambda used to define it, except that it “captures”
return
. In other words, return
will fall
through lambdas not defined with fn
, and continues until it
exits a function defined with fn
:
~> fn f {
{ echo a; return }
echo b # will not execute
}
~> f
a
~> {
f
echo c # executed, because f "captures" the return
}
a
c
TODO: Find a better way to describe this. Hopefully the example is illustrative enough, though.
The lambda may refer to the function being defined. This makes it easy to define recursive functions:
~> fn f {|n| if (== $n 0) { put 1 } else { * $n (f (- $n 1)) } }
~> f 3
▶ (num 6)
Under the hood, fn
defines a variable with the given
name plus ~
(see variable
suffix). Example:
~> fn f { echo hello from f }
~> var v = $f~
~> $v
hello from f
8.11 Language pragmas:
pragma
The pragma
special command can be used to set
pragmas that affect the behavior of the Elvish
language. The syntax looks like:
pragma <name> = <value>
The name must appear literally. The value must also appear literally, unless otherwise specified.
Pragmas apply from the point it appears, to the end of the lexical scope it appears in, including subscopes.
The following pragmas are available:
The
unknown-command
pragma affects the resolution of command heads, and can take one of two values,external
(the default) anddisallow
. See ordinary command for details.Note:
pragma unknown-command = disallow
enables a style where uses of external commands must be explicitly via thee:
namespace. You can also explicitly declare a set of external commands to use directly, like the following:pragma unknown-command = disallow
var ls = $e:ls~
var cat = $e:cat~
# ls and cat can be used directly;
# other external commands must be prefixed with e:
9 Pipeline
A pipeline is formed by joining one or more commands
together with the pipe sign (|
).
For each pair of adjacent commands a | b
, the standard
output of the left-hand command a
(IO port 1) is connected
to the standard input (IO port 0) of the right-hand command
b
. Both the file and the value channel are connected, even
if one of them is not used.
Elvish may have internal buffering for both the file and the value
channel, so a
may be able to write bytes or values even if
b
is not reading them. The exact buffer size is not
specified.
Command redirections are applied before the connection happens. For
instance, the following writes foo
to a.txt
instead of the output:
~> echo foo > a.txt | cat
~> cat a.txt
foo
A pipeline runs all of its command in parallel, and terminates when all of the commands have terminated.
9.1 Pipeline exception
If one or more command in a pipeline throws an exception, the other commands will continue to execute as normal. After all commands finish execution, an exception is thrown, the value of which depends on the number of commands that have thrown an exception:
If only one command has thrown an exception, that exception is rethrown.
If more than one commands have thrown exceptions, a “composite exception”, containing information all exceptions involved, is thrown.
If a command threw an exception because it tried to write output when the next command has terminated, that exception is suppressed when it is propagated to the pipeline.
For example, the put
command throws an exception when
trying to write to a closed pipe, so the following loop will terminate
with an exception:
~> while $true { put foo } > &-
Exception: port has no value output
[tty 9], line 1: while $true { put foo } > &-
However, if it appears in a pipeline before nop
, the
entire pipeline will not throw an exception:
~> while $true { put foo } | nop
~> # no exception thrown from previous line
Internally, the put foo
command still threw an
exception, but since that exception was trying to write to output when
nop
already terminated, that exception was suppressed by
the pipeline.
This can be more clearly observed with the following code:
~> var r = $false
~> { while $true { put foo }; set r = $true } | nop
~> put $r
▶ $false
The same mechanism works for builtin commands that write to the byte output:
~> var r = $false
~> { while $true { echo foo }; set r = $true } | nop
~> put $r
▶ $false
On UNIX, if an external command was terminated by SIGPIPE, and Elvish
detected that it terminated after the next command in the pipeline, such
exceptions will also be suppressed by the pipeline. For example, the
following pipeline does not throw an exception, despite the
yes
command being killed by SIGPIPE:
~> yes | head -n1
y
9.2 Background pipeline
Adding an ampersand &
to the end of a pipeline will
cause it to be executed in the background. In this case, the rest of the
code chunk will continue to execute without waiting for the pipeline to
finish. Exceptions thrown from the background pipeline do not affect the
code chunk that contains it.
When a background pipeline finishes, a message is printed to the terminal if the shell is interactive.
10 Code Chunk
A code chunk is formed by joining zero or more pipelines together, separating them with either newlines or semicolons.
Pipelines in a code chunk are executed in sequence. If any pipeline throws an exception, the execution of the whole code chunk stops, propagating that exception.
11 Exception and Flow Commands
Exceptions have similar semantics to those in Python or Java. They
can be thrown with the fail command and
caught with either exception capture ?()
or the
try
special command.
If an external command exits with a non-zero status, Elvish treats that as an exception.
Flow commands – break
, continue
and
return
– are ordinary builtin commands that raise special
“flow control” exceptions. The for
, while
, and
peach
commands capture break
and
continue
, while fn
modifies its closure to
capture return
.
One interesting implication is that since flow commands are just
ordinary commands you can build functions on top of them. For instance,
this function break
s randomly:
fn random-break {
if eq (randint 2) 0 {
break
}
}
The function random-break
can then be used in for-loops
and while-loops.
Note that the return
flow control exception is only
captured by functions defined with fn
. It falls through
ordinary lambdas:
fn f {
{
# returns f, falling through the innermost lambda
return
}
}
12 Namespaces and Modules
Like other modern programming languages, but unlike traditional shells, Elvish has a namespace mechanism for preventing name collisions.
12.1 Syntax
Prepend namespace:
to command names and variable names
to specify the namespace. The following code
e:echo $E:PATH
uses the echo
command from the e:
namespace
and the PATH
variable from the E:
namespace.
The colon is considered part of the namespace name.
Namespaces may be nested; for example, calling
edit:location:start
first finds the edit:
namespace, and then the location:
namespace inside it, and
then call the start
function within the nested
namespace.
12.2 Special namespaces
The following namespaces have special meanings to the language:
e:
refers to externals. For instance,e:ls
refers to the external commandls
.Most of the time you can rely on static resolution rules of ordinary commands and do not need to use this explicitly, unless a function defined by you (or an Elvish builtin) shadows an external command.
E:
refers to environment variables. For instance,$E:USER
is the environment variableUSER
.This is always needed, because unlike command resolution, variable resolution does not fall back onto environment variables.
12.3 Modules
Apart from the special namespaces, the most common usage of namespaces is to reference modules, reusable pieces of code that are either shipped with Elvish itself or defined by the user.
12.3.1 Importing modules with
use
Modules are imported using the use
special command. It
requires a module spec and allows a namespace
alias:
use $spec $alias?
The module spec and the alias must both be a simple string literal. Compound
strings such as 'a'/b
are not allowed.
The module spec specifies which module to import. The alias, if given, specifies the namespace to import the module under. By default, the namespace is derived from the module spec by taking the part after the last slash.
Module specs fall into three categories that are resolved in the following order:
Relative: These are relative to the file containing the
use
command.User defined: These match a user defined module in a module search directory.
Pre-defined: These match the name of a pre-defined module, such as
math
orstr
.
If a module spec doesn’t match any of the above a “no such module” exception is raised.
Examples:
use str # imports the "str" module as "str:"
use a/b/c # imports the "a/b/c" module as "c:"
use a/b/c foo # imports the "a/b/c" module as "foo:"
12.3.2 Pre-defined modules
Elvish’s standard library provides the following pre-defined modules
that can be imported by the use
command:
- builtin
- edit: only available in interactive mode. As
a special case it does not need importing via
use
, but this may change in the future. - epm
- math
- path
- platform
- re
- readline-binding
- store
- str
- unix: only available on UNIX-like platforms
(see
$platform:is-unix
)
12.3.3 User-defined modules
You can define your own modules in Elvish by putting them under one
of the module search
directories and giving them a .elv
extension (but see
relative imports for an alternative).
For instance, to define a module named a
, you can put the
following in ~/.config/elvish/lib/a.elv
(on Windows,
replace ~/.config
with ~\AppData\Roaming
):
~> cat ~/.config/elvish/lib/a.elv
echo "mod a loading"
fn f {
echo "f from mod a"
}
This module can now be imported by use a
:
~> use a
mod a loading
~> a:f
f from mod a
Similarly, a module defined in
~/.config/elvish/lib/x/y/z.elv
can be imported by
use x/y/z
:
~> cat .config/elvish/lib/x/y/z.elv
fn f {
echo "f from x/y/z"
}
~> use x/y/z
~> z:f
f from x/y/z
In general, a module defined in namespace will be the same as the
file name (without the .elv
extension).
There is experimental support for importing modules written in Go. See the project repository for details.
12.3.4 Circular dependencies
Circular dependencies are allowed but has an important restriction.
If a module a
contains use b
and module
b
contains use a
, the top-level statements in
module b
will only be able to access variables that are
defined before the use b
in module a
; other
variables will be $nil
.
On the other hand, functions in module b
will have
access to bindings in module a
after it is fully
evaluated.
Examples:
~> cat a.elv
var before = before
use ./b
var after = after
~> cat b.elv
use ./a
put $a:before $a:after
fn f { put $a:before $a:after }
~> use ./a
▶ before
▶ $nil
~> use ./b
~> b:f
▶ before
▶ after
Note that this behavior can be different depending on whether the
REPL imports a
or b
first. In the previous
example, if the REPL imports b
first, it will have access
to all the variables in a
:
~> use ./b
▶ before
▶ after
Note: Elvish caches imported modules. If you are
trying this locally, run a fresh Elvish instance with exec
first.
When you do need to have circular dependencies, it is best to avoid using variables from the modules in top-level statements, and only use them in functions.
12.3.5 Relative imports
The module spec may begin with ./
or ../
to
introduce a relative import. When use
is
invoked from a file this will import the file relative to the location
of the file. When use
is invoked from an interactive
prompt, this will import the file relative to the current working
directory.
12.3.6 Scoping of imports
Namespace imports are lexically scoped. For instance, if you
use
a module within an inner scope, it is not available
outside that scope:
{
use some-mod
some-mod:some-func
}
some-mod:some-func # not valid
The imported modules themselves are also evaluated in a separate
scope. That means that functions and variables defined in the module
does not pollute the default namespace, and vice versa. For instance, if
you define ls
as a wrapper function in your rc.elv
:
fn ls {|@a|
e:ls --color=auto $@a
}
That definition is not visible in module files: ls
will
still refer to the external command ls
, unless you shadow
it in the very same module.
12.3.7 Re-importing
Modules are cached after one import. Subsequent imports do not
re-execute the module; they only serve the bring it into the current
scope. Moreover, the cache is keyed by the path of the module, not the
name under which it is imported. For instance, if you have the following
in ~/.config/elvish/lib/a/b.elv
:
echo importing
The following code only prints one importing
:
{ use a/b }
use a/b # only brings mod into the lexical scope
As does the following:
use a/b
use a/b alias