Nushell
Get Nu!
Getting Started
  • The Nushell Book
  • Command Reference
  • Cookbook
  • Language Reference Guide
  • Contributing Guide
Blog
  • English
  • 中文
  • Deutsch
  • Français
  • Español
  • 日本語
  • Português do Brasil
  • Русский язык
GitHub
Get Nu!
Getting Started
  • The Nushell Book
  • Command Reference
  • Cookbook
  • Language Reference Guide
  • Contributing Guide
Blog
  • English
  • 中文
  • Deutsch
  • Français
  • Español
  • 日本語
  • Português do Brasil
  • Русский язык
GitHub
  • Introduction
  • Installation
    • Default Shell
  • Getting Started
    • Quick Tour
    • Moving Around the System
    • Thinking in Nu
    • Nushell Cheat Sheet
  • Nu Fundamentals
    • Types of Data
    • Loading Data
    • Pipelines
    • Working with Strings
    • Working with Lists
    • Working with Records
    • Working with Tables
    • Navigating and Accessing Structured Data
    • Special Variables
  • Programming in Nu
    • Custom Commands
    • Aliases
    • Operators
    • Variables
    • Control Flow
    • Scripts
    • Modules
      • Using Modules
      • Creating Modules
    • Overlays
    • Sorting
    • Testing your Nushell Code
    • Best Practices
  • Nu as a Shell
    • Configuration
    • Environment
    • Stdout, Stderr, and Exit Codes
    • Running System (External) Commands
    • How to Configure 3rd Party Prompts
    • Directory Stack
    • Reedline, Nu's Line Editor
    • Custom Completions
    • Externs
    • Coloring and Theming in Nu
    • Hooks
    • Background Jobs
  • Coming to Nu
    • Coming from Bash
    • Coming from CMD.EXE
    • Nu map from other shells and domain specific languages
    • Nu Map from Imperative Languages
    • Nu Map from Functional Languages
    • Nushell operator map
  • Design Notes
    • How Nushell Code Gets Run
  • (Not So) Advanced
    • Standard Library (Preview)
    • Dataframes
    • Metadata
    • Creating Your Own Errors
    • Parallelism
    • Plugins
    • explore

Thinking in Nu

Nushell is different! It's common (and expected!) for new users to have some existing "habits" or mental models coming from other shells or languages.

The most common questions from new users typically fall into one of the following topics:

  • Nushell isn't Bash
    • It can sometimes look like Bash
    • But it's not Bash
  • Implicit Return
  • Single Return Value per Expression
  • Every Command Returns a Value
  • Think of Nushell as a Compiled Language
    • Features Built on Static Parsing
    • Limitations
    • Summary
  • Variables are Immutable by Default
  • Nushell's Environment is Scoped

Nushell isn't Bash

It can sometimes look like Bash

Nushell is both a programming language and a shell. Because of this, it has its own way of working with files, directories, websites, and more. You'll find that some features in Nushell work similar to those you're familiar with in other shells. For instance, pipelines work by combining two (or more) commands together, just like in other shells.

For example, the following commandline works the same in both Bash and Nushell on Unix/Linux platforms:

curl -s https://api.github.com/repos/nushell/nushell/contributors | jq -c '.[] | {login,contributions}'
# => returns contributors to Nushell, ordered by number of contributions

Nushell has many other similarities with Bash (and other shells) and many commands in common.

Tips

Bash is primarily a command interpreter which runs external commands. Nushell provides many of these as cross-platform, built-in commands.

While the above commandline works in both shells, in Nushell there's just no need to use the curl and jq commands. Instead, Nushell has a built-in http get command and handles JSON data natively. For example:

http get https://api.github.com/repos/nushell/nushell/contributors | select login contributions

Thinking in Nushell

Nushell borrows concepts from many shells and languages. You'll likely find many of Nushell's features familiar.

But it's not Bash

Because of this, however, it's sometimes easy to forget that some Bash (and POSIX in general) style constructs just won't work in Nushell. For instance, in Bash, it would be normal to write:

# Redirect using >
echo "hello" > output.txt
# But compare (greater-than) using the test command
test 4 -gt 7
echo $?
# => 1

In Nushell, however, the > is used as the greater-than operator for comparisons. This is more in line with modern programming expectations.

4 > 10
# => false

Since > is an operator, redirection to a file in Nushell is handled through a pipeline command that is dedicated to saving content - save:

"hello" | save output.txt

Thinking in Nushell

We've put together a list of common Bash'isms and how to accomplish those tasks in Nushell in the Coming from Bash Chapter.

Implicit Return

Users coming from other shells will likely be very familiar with the echo command. Nushell's echo might appear the same at first, but it is very different.

First, notice how the following output looks the same in both Bash and Nushell (and even PowerShell and Fish):

echo "Hello, World"
# => Hello, World

But while the other shells are sending Hello, World straight to standard output, Nushell's echo is simply returning a value. Nushell then renders the return value of a command, or more technically, an expression.

More importantly, Nushell implicitly returns the value of an expression. This is similar to PowerShell or Rust in many respects.

Tips

An expression can be more than just a pipeline. Even custom commands (similar to functions in many languages, but we'll cover them more in depth in a later chapter) automatically, implicitly return the last value. There's no need for an echo or even a return command to return a value - It just happens.

In other words, the string "Hello, World" and the output value from echo "Hello, World" are equivalent:

"Hello, World" == (echo "Hello, World")
# => true

Here's another example with a custom command definition:

def latest-file [] {
    ls | sort-by modified | last
}

The output of that pipeline (its "value") becomes the return value of the latest-file custom command.

Thinking in Nushell

Most anywhere you might write echo <something>, in Nushell, you can just write <something> instead.

Single Return Value per Expression

It's important to understand that an expression can only return a single value. If there are multiple subexpressions inside an expression, only the last value is returned.

A common mistake is to write a custom command definition like this:

def latest-file [] {
    echo "Returning the last file"
    ls | sort-by modified | last
}

latest-file

New users might expect:

  • Line 2 to output "Returning the last file"
  • Line 3 to return/output the file

However, remember that echo returns a value. Since only the last value is returned, the Line 2 value is discarded. Only the file will be returned by line 3.

To make sure the first line is displayed, use the print command:

def latest-file [] {
    print "Returning last file"
    ls | sort-by modified | last
}

Also compare:

40; 50; 60

Tips

A semicolon is the same as a newline in a Nushell expression. The above is the same as a file or multi-line command:

40
50
60

or

echo 40
echo 50
echo 60

See Also: Multi-line Editing

In all of the above:

  • The first value is evaluated as the integer 40 but is not returned
  • The second value is evaluated as the integer 50 but is not returned
  • The third value is evaluated as the integer 60, and since it is the last value, it is is returned and displayed (rendered).

Thinking in Nushell

When debugging unexpected results, be on the lookout for:

  • Subexpressions (e.g., commands or pipelines) that ...
  • ... output a (non-null) value ...
  • ... where that value isn't returned from the parent expression.

These can be likely sources of issues in your code.

Every Command Returns a Value

Some languages have the concept of "statements" which don't return values. Nushell does not.

In Nushell, every command returns a value, even if that value is null (the nothing type). Consider the following multiline expression:

let p = 7
print $p
$p * 6
  1. Line 1: The integer 7 is assigned to $p, but the return value of the let command itself is null. However, because it is not the last value in the expression, it is not displayed.
  2. Line 2: The return value of the print command itself is null, but the print command forces its argument ($p, which is 7) to be displayed. As with Line 1, the null return value is discarded since this isn't the last value in the expression.
  3. Line 3: Evaluates to the integer value 42. As the last value in the expression, this is the return result, and is also displayed (rendered).

Thinking in Nushell

Becoming familiar with the output types of common commands will help you understand how to combine simple commands together to achieve complex results.

help <command> will show the signature, including the output type(s), for each command in Nushell.

Think of Nushell as a Compiled Language

In Nushell, there are exactly two, separate, high-level stages when running code:

  1. Stage 1 (Parser): Parse the entire source code
  2. Stage 2 (Engine): Evaluate the entire source code

It can be useful to think of Nushell's parsing stage as compilation in static languages like Rust or C++. By this, we mean that all of the code that will be evaluated in Stage 2 must be known and available during the parsing stage.

Important

However, this also means that Nushell cannot currently support an eval construct as with dynamic languages such as Bash or Python.

Features Built on Static Parsing

On the other hand, the static results of Parsing are key to many features of Nushell its REPL, such as:

  • Accurate and expressive error messages
  • Semantic analysis for earlier and robust detection of error conditions
  • IDE integration
  • The type system
  • The module system
  • Completions
  • Custom command argument parsing
  • Syntax highlighting
  • Real-time error highlighting
  • Profiling and debugging commands
  • (Future) Formatting
  • (Future) Saving IR (Intermediate Representation) "compiled" results for faster execution

Limitations

The static nature of Nushell often leads to confusion for users coming to Nushell from languages where an eval is available.

Consider a simple two-line file:

<line1 code>
<line2 code>
  1. Parsing:
    1. Line 1 is parsed
    2. Line 2 is parsed
  2. If parsing was successful, then Evaluation:
    1. Line 1 is evaluated
    2. Line 2 is evaluated

This helps demonstrate why the following examples cannot run as a single expression (e.g., a script) in Nushell:

Note

The following examples use the source command, but similar conclusions apply to other commands that parse Nushell source code, such as use, overlay use, hide or source-env.

Example: Dynamically Generating Source

Consider this scenario:

"print Hello" | save output.nu
source output.nu
# => Error: nu::parser::sourced_file_not_found
# =>
# =>   × File not found
# =>    ╭─[entry #5:2:8]
# =>  1 │ "print Hello" | save output.nu
# =>  2 │ source output.nu
# =>    ·        ────┬────
# =>    ·            ╰── File not found: output.nu
# =>    ╰────
# =>   help: sourced files need to be available before your script is run

This is problematic because:

  1. Line 1 is parsed but not evaluated. In other words, output.nu is not created during the parsing stage, but only during evaluation.
  2. Line 2 is parsed. Because source is a parser-keyword, resolution of the sourced file is attempted during Parsing (Stage 1). But output.nu doesn't even exist yet! If it does exist, then it's probably not even the correct file! This results in the error.

Note

Typing these as two separate lines in the REPL will work since the first line will be parsed and evaluated, then the second line will be parsed and evaluated.

The limitation only occurs when both are parsed together as a single expression, which could be part of a script, block, closure, or other expression.

See the REPL section in "How Nushell Code Gets Run" for more explanation.

Example: Dynamically Creating a Filename to be Sourced

Another common scenario when coming from another shell might be attempting to dynamically create a filename that will be sourced:

let my_path = "~/nushell-files"
source $"($my_path)/common.nu"
# => Error:
# =>   × Error: nu::shell::not_a_constant
# =>   │
# =>   │   × Not a constant.
# =>   │    ╭─[entry #6:2:11]
# =>   │  1 │ let my_path = "~/nushell-files"
# =>   │  2 │ source $"($my_path)/common.nu"
# =>   │    ·           ────┬───
# =>   │    ·               ╰── Value is not a parse-time constant
# =>   │    ╰────
# =>   │   help: Only a subset of expressions are allowed constants during parsing. Try using the 'const' command or typing the value literally.
# =>   │
# =>    ╭─[entry #6:2:8]
# =>  1 │ let my_path = "~/nushell-files"
# =>  2 │ source $"($my_path)/common.nu"
# =>    ·        ───────────┬───────────
# =>    ·                   ╰── Encountered error during parse-time evaluation
# =>    ╰────

Because the let assignment is not resolved until evaluation, the parser-keyword source will fail during parsing if passed a variable.

Comparing Rust and C++

Imagine that the code above was written in a typical compiled language such as C++:

#include <string>

std::string my_path("foo");
#include <my_path + "/common.h">

or Rust

let my_path = "foo";
use format!("{}::common", my_path);

If you've ever written a simple program in any of these languages, you can see these examples aren't valid in those languages. Like Nushell, compiled languages require that all of the source code files are ready and available to the compiler beforehand.

See Also

As noted in the error message, however, this can work if my_path can be defined as a constant since constants can be (and are) resolved during parsing.

const my_path = "~/nushell-files"
source $"($my_path)/common.nu"

See Parse-time Constant Evaluation for more details.

Example: Change to a different directory (cd) and source a file

Here's one more — Change to a different directory and then attempt to source a file in that directory.

if ('spam/foo.nu' | path exists) {
    cd spam
    source-env foo.nu
}

Based on what we've covered about Nushell's Parse/Eval stages, see if you can spot the problem with that example.

Solution

In line 3, during Parsing, the source-env attempts to parse foo.nu. However, cd doesn't occur until Evaluation. This results in a parse-time error, since the file is not found in the current directory.

To resolve this, of course, simply use the full-path to the file to be sourced.

    source-env spam/foo.nu

Summary

Important

For a more in-depth explanation of this section, see How Nushell Code Gets Run.

Thinking in Nushell

Nushell is designed to use a single Parsing stage for each expression or file. This Parsing stage occurs before and is separate from Evaluation. While this enables many of Nushell's features, it also means that users need to understand the limitations it creates.

Variables are Immutable by Default

Another common surprise when coming from other languages is that Nushell variables are immutable by default. While Nushell has optional mutable variables, many of Nushell's commands are based on a functional-style of programming which requires immutability.

Immutable variables are also key to Nushell's par-each command, which allows you to operate on multiple values in parallel using threads.

See Immutable Variables and Choosing between mutable and immutable variables for more information.

Thinking in Nushell

If you're used to relying on mutable variables, it may take some time to relearn how to code in a more functional style. Nushell has many functional features and commands that operate on and with immutable variables. Learning them will help you write code in a more Nushell-idiomatic style.

A nice bonus is the performance increase you can realize by running parts of your code in parallel with par-each.

Nushell's Environment is Scoped

Nushell takes multiple design cues from compiled languages. One such cue is that languages should avoid global mutable state. Shells have commonly used global mutation to update the environment, but Nushell attempts to steer clear of this approach.

In Nushell, blocks control their own environment. Changes to the environment are scoped to the block where they occur.

In practice, this lets you write (as just one example) more concise code for working with subdirectories. Here's an example that builds each sub-project in the current directory:

ls | each { |row|
  cd $row.name
  make
}

The cd command changes the PWD environment variables, but this variable change does not survive past the end of the block. This allows each iteration to start from the current directory and then enter the next subdirectory.

Having a scoped environment makes commands more predictable, easier to read, and when the time comes, easier to debug. It's also another feature that is key to the par-each command we discussed above.

Nushell also provides helper commands like load-env as a convenient way of loading multiple updates to the environment at once.

See Also

Environment - Scoping

Note

def --env is an exception to this rule. It allows you to create a command that changes the parent's environment.

Thinking in Nushell

Use scoped-environment to write more concise scripts and prevent unnecessary or unwanted global environment mutation.

Edit this page on GitHub
Contributors: JT, jntrnr, rgwood, Reilly Wood, Fernando Herrera, Evan Platzer, Justin Ma, Jonas Gollenz, Jakub Žádník, HoLLy, Leon, Joel Afriyie, Hofer-Julian, Alpha Chen, Máté FARKAS, Stefan Holderbach, Mate Farkas, Jan Klass, arnau, Trent-Fellbootman, ysthakur, fdncred, Wind, Ian Manske, sophiajt, NotTheDr01ds, Bruce Weirdan, jesper-olsen, Wouter Overmeire, Kai Welke, Gisle Aas
Prev
Moving Around the System
Next
Nushell Cheat Sheet