Control Flow
Nushell provides several commands that help determine how different groups of code are executed. In programming languages this functionality is often referred to as control flow.
Tips
One thing to note is that all of the commands discussed on this page use blocks. This means you can mutate environmental variables and other mutable variables in them.
Already covered
Below we cover some commands related to control flow, but before we get to them, it's worthwhile to note there are several pieces of functionality and concepts that have already been covered in other sections that are also related to control flow or that can be used in the same situations. These include:
- Pipes on the pipelines page.
- Closures on the types of data page.
- Iteration commands on the working with lists page. Such as:
Choice (Conditionals)
The following commands execute code based on some given condition.
Tips
The choice/conditional commands are expressions so they return values, unlike the other commands on this page. This means the following works.
> 'foo' | if $in == 'foo' { 1 } else { 0 } | $in + 2
3
if
if
evaluates branching blocks of code based on the results of one or more conditions similar to the "if" functionality in other programming languages. For example:
> if $x > 0 { 'positive' }
Returns 'positive
' when the condition is true
($x
is greater than zero) and null
when the condition is false
($x
is less than or equal to zero).
We can add an else
branch to the if
after the first block which executes and returns the resulting value from the else
block when the condition is false
. For example:
> if $x > 0 { 'positive' } else { 'non-positive' }
This time it returns 'positive'
when the condition is true
($x
is greater than zero) and 'non-positive
' when the condition is false
($x
is less than or equal to zero).
We can also chain multiple if
s together like the following:
> if $x > 0 { 'positive' } else if $x == 0 { 'zero' } else { "negative" }
When the first condition is true
($x
is greater than zero) it will return 'positive'
, when the first condition is false
and the next condition is true
($x
equals zero) it will return 'zero'
, otherwise it will return 'negative'
(when $x
is less than zero).
match
match
executes one of several conditional branches based on the value given to match. You can also do some pattern matching to unpack values in composite types like lists and records.
Basic usage of match
can conditionally run different code like a "switch" statement common in other languages. match
checks if the value after the word match
is equal to the value at the start of each branch before the =>
and if it does, it executes the code after that branch's =>
.
> match 3 {
1 => 'one',
2 => {
let w = 'w'
't' + $w + 'o'
},
3 => 'three',
4 => 'four'
}
three
The branches can either return a single value or, as shown in the second branch, can return the results of a block.
Catch all Branch
You can also have a catch all condition for when the given value doesn't match any of the other conditions by having a branch whose matching value is _
.
> let foo = match 7 {
1 => 'one',
2 => 'two',
3 => 'three',
_ => 'other number'
}
> $foo
other number
(Reminder, match
is an expression which is why we can assign the result to $foo
here).
Pattern Matching
You can "unpack" values from types like lists and records with pattern matching. You can then assign variables to the parts you want to unpack and use them in the matched expressions.
> let foo = { name: 'bar', count: 7 }
> match $foo {
{ name: 'bar', count: $it } => ($it + 3),
{ name: _, count: $it } => ($it + 7),
_ => 1
}
10
The _
in the second branch means it matches any record with field name
and count
, not just ones where name
is 'bar'
.
Guards
You can also add an additional condition to each branch called a "guard" to determine if the branch should be matched. To do so, after the matched pattern put if
and then the condition before the =>
.
> let foo = { name: 'bar', count: 7 }
> match $foo {
{ name: 'bar', count: $it } if $it < 5 => ($it + 3),
{ name: 'bar', count: $it } if $it >= 5 => ($it + 7),
_ => 1
}
14
You can find more details about match
in the pattern matching cookbook page.
Loops
The loop commands allow you to repeat a block of code multiple times.
Loops and Other Iterating Commands
The functionality of the loop commands is similar to commands that apply a closure over elements in a list or table like each
or where
and many times you can accomplish the same thing with either. For example:
> mut result = []
> for $it in [1 2 3] { $result = ($result | append ($it + 1)) }
> $result
╭───┬───╮
│ 0 │ 2 │
│ 1 │ 3 │
│ 2 │ 4 │
╰───┴───╯
> [1 2 3] | each { $in + 1 }
╭───┬───╮
│ 0 │ 2 │
│ 1 │ 3 │
│ 2 │ 4 │
╰───┴───╯
While it may be tempting to use loops if you're familiar with them in other languages, it is considered more in the Nushell-style (idiomatic) to use commands that apply closures when you can solve a problem either way. The reason for this is because of a pretty big downside with using loops.
Loop Disadvantages
The biggest downside of loops is that they are statements, unlike each
which is an expression. Expressions, like each
always result in some output value, however statements do not.
This means that they don't work well with immutable variables and using immutable variables is considered a more Nushell-style. Without a mutable variable declared beforehand in the example in the previous section, it would be impossible to use for
to get the list of numbers with incremented numbers, or any value at all.
Statements also don't work in Nushell pipelines which require some output. In fact Nushell will give an error if you try:
> [1 2 3] | for x in $in { $x + 1 } | $in ++ [5 6 7]
Error: nu::parser::unexpected_keyword
× Statement used in pipeline.
╭─[entry #5:1:1]
1 │ [1 2 3] | for x in $in { $x + 1 } | $in ++ [5 6 7]
· ─┬─
· ╰── not allowed in pipeline
╰────
help: 'for' keyword is not allowed in pipeline. Use 'for' by itself, outside of a pipeline.
Because Nushell is very pipeline oriented, this means using expression commands like each
is typically more natural than loop statements.
Loop Advantages
If loops have such a big disadvantage, why do they exist? Well, one reason is that closures, like each
uses, can't modify mutable variables in the surrounding environment. If you try to modify a mutable variable in a closure you will get an error:
> mut foo = []
> [1 2 3] | each { $foo = ($foo | append ($in + 1)) }
Error: nu::parser::expected_keyword
× Capture of mutable variable.
╭─[entry #8:1:1]
1 │ [1 2 3] | each { $foo = ($foo | append ($in + 1)) }
· ──┬─
· ╰── capture of mutable variable
╰────
If you modify an environmental variable in a closure, you can, but it will only modify it within the scope of the closure, leaving it unchanged everywhere else. Loops, however, use blocks which means they can modify a regular mutable variable or an environmental variable within the larger scope.
> mut result = []
> for $it in [1 2 3] { $result = ($result | append ($it + 1)) }
> $result
╭───┬───╮
│ 0 │ 2 │
│ 1 │ 3 │
│ 2 │ 4 │
╰───┴───╯
for
for
loops over a range or collection like a list or a table.
> for x in [1 2 3] { $x * $x | print }
1
4
9
Expression Command Alternatives
while
while
loops the same block of code until the given condition is false
.
> mut x = 0; while $x < 10 { $x = $x + 1 }; $x
10
Expression Command Alternatives
The "until" and other "while" commands
loop
loop
loops a block infinitely. You can use break
(as described in the next section) to limit how many times it loops. It can also be handy for continuously running scripts, like an interactive prompt.
> mut x = 0; loop { if $x > 10 { break }; $x = $x + 1 }; $x
11
break
break
will stop executing the code in a loop and resume execution after the loop. Effectively "break"ing out of the loop.
> for x in 1..10 { if $x > 3 { break }; print $x }
1
2
3
continue
continue
will stop execution of the current loop, skipping the rest of the code in the loop, and will go to the next loop. If the loop would normally end, like if for
has iterated through all the given elements, or if while
's condition is now false, it won't loop again and execution will continue after the loop block.
> mut x = -1; while $x <= 6 { $x = $x + 1; if $x mod 3 == 0 { continue }; print $x }
1
2
4
5
7
Errors
error make
error make
creates an error that stops execution of the code and any code that called it, until either it is handled by a try
block, or it ends the script and outputs the error message. This functionality is the same as "exceptions" in other languages.
> print 'printed'; error make { msg: 'Some error info' }; print 'unprinted'
printed
Error: × Some error info
╭─[entry #9:1:1]
1 │ print 'printed'; error make { msg: 'Some error info' }; print 'unprinted'
· ─────┬────
· ╰── originates from here
╰────
The record passed to it provides some information to the code that catches it or the resulting error message.
You can find more information about error make
and error concepts on the Creating your own errors page.
try
try
will catch errors created anywhere in the try
's code block and resume execution of the code after the block.
> try { error make { msg: 'Some error info' }}; print 'Resuming'
Resuming
This includes catching built in errors.
> try { 1 / 0 }; print 'Resuming'
Resuming
The resulting value will be nothing
if an error occurs and the returned value of the block if an error did not occur.
If you include a catch
block after the try
block, it will execute the code in the catch
block if an error occurred in the try
block.
> try { 1 / 0 } catch { 'An error happened!' } | $in ++ ' And now I am resuming.'
An error happened! And now I am resuming.
It will not execute the catch
block if an error did not occur.
Other
return
return
Ends a closure or command early where it is called, without running the rest of the command/closure, and returns the given value. Not often necessary since the last value in a closure or command is also returned, but it can sometimes be convenient.
def 'positive-check' [it] {
if $it > 0 {
return 'positive'
};
'non-positive'
}
> positive-check 3
positive
> positive-check (-3)
non-positive
> let positive_check = {|elt| if $elt > 0 { return 'positive' }; 'non-positive' }
> do $positive_check 3
positive
> do $positive_check (-3)
non-positive