How To

Install and Use Nu Shell (Nushell) on Linux and macOS

Nu shell hands the next command a table instead of text, so ls | where size > 10MB works without a single awk or cut. That one idea is why people try Nushell. It is also why half the snippets you find online break, because the project ships a release about every six weeks, and commands such as fetch and let-env have been gone for years.

Original content from computingforgeeks.com - post 86069

This guide installs Nushell from the project’s own package repositories on Ubuntu, Debian and Fedora, or from the release tarball on any Linux box, then puts it to work on files, processes and CSV, JSON and YAML data. It also covers the config file, making Nu your login shell, and the old commands that now fail. We ran the steps in October 2026 with Nushell 0.116.1 on Ubuntu 26.04, Ubuntu 24.04 and Fedora 45, and the macOS notes at the end come from the Nushell docs rather than our lab.

Choose an install method

Neither Ubuntu nor Debian packages Nushell, and Fedora’s own nushell package was still at 0.99.1 on Fedora 45, from before the 0.101 config rework. That gap matters because the current docs and every example below assume a recent release, so a distro build would fail on half of them. Install from one of these instead:

  • Ubuntu and Debian: the official Gemfury apt repository
  • Fedora, Rocky Linux and AlmaLinux: the official Gemfury yum repository
  • Any Linux distribution: the static musl tarball from GitHub releases
  • macOS: Homebrew, covered in the macOS notes near the end

A package repository is the better choice on a machine you keep, because apt upgrade or dnf upgrade then picks up each new release. With a release roughly every six weeks, a manual install falls several versions behind within a few months, and that is exactly how readers end up following docs that no longer match their binary. The tarball suits a container, a jump host or any box where you don’t want a third-party repository. A snap also exists, but it comes from a community publisher rather than the Nushell project, so we left it out.

Install Nu Shell on Ubuntu and Debian

The repository signs its InRelease file, so add its key to a keyring of its own and point the source at that keyring. Scoping the key with signed-by means it can only vouch for this one repository, not for the Ubuntu archive. The /etc/apt/keyrings directory already exists on Ubuntu 26.04 and 24.04. On a minimal Debian image, which we did not test, run sudo apt install -y wget gpg and sudo install -d -m 0755 /etc/apt/keyrings first:

wget -qO- https://apt.fury.io/nushell/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/fury-nushell.gpg
echo "deb [signed-by=/etc/apt/keyrings/fury-nushell.gpg] https://apt.fury.io/nushell/ /" | sudo tee /etc/apt/sources.list.d/fury-nushell.list

Refresh the index and install the package.

sudo apt update
sudo apt install -y nushell

It is one package with no dependencies, but it is not small. Apt reported 249 MB on both Ubuntu releases, and 114 MB of that is the Polars dataframe plugin. The package also adds /usr/bin/nu to /etc/shells, which matters later if you make Nu your login shell. Confirm the version:

nu --version

It prints the bare version number.

0.116.1

On the day we tested, that matched the latest release on GitHub, so the apt repository was not lagging behind upstream.

Install Nushell on Fedora, Rocky Linux and AlmaLinux

Skip dnf install nushell on a stock Fedora system, because it pulls Fedora’s 0.99.1 build. EPEL carries the same 0.99.1 build for Rocky and AlmaLinux. The repository below is the one the Nushell docs give for the whole Red Hat family; we tested it on Fedora 45, whose dnf5 output is quoted here, and Rocky or AlmaLinux with dnf4 print slightly different messages. Add the project’s repository instead. The upstream instructions set gpgcheck=0, and that’s needed: when we checked, the RPMs carried no signature at all (rpm -K reported SIGNATURES NOT OK). The repository metadata is signed, though, so we added repo_gpgcheck=1 to make dnf verify the metadata against the published key. Create the file (on a minimal server, sudo dnf install -y vim-enhanced first, because vim-minimal only ships vi):

sudo vim /etc/yum.repos.d/fury-nushell.repo

Add the repository definition, then save the file.

[gemfury-nushell]
name=Gemfury Nushell Repo
baseurl=https://yum.fury.io/nushell/
enabled=1
gpgcheck=0
repo_gpgcheck=1
gpgkey=https://yum.fury.io/nushell/gpg.key

Build the metadata cache for the new repository first. Leave out -y if you want dnf to stop and ask before it trusts the key; with -y it imports the key straight away and prints the fingerprint, which should read F7939B750FA1789832D67A809E755FC365BF00D5:

sudo dnf makecache -y --repo gemfury-nushell

With the key in place, install the package:

sudo dnf install -y nushell

Expect the line Warning: skipped OpenPGP checks for 1 package from repository: gemfury-nushell at the end. That warning is about the unsigned RPM again, and the signed metadata is what protects you here, since it pins the checksum of every package dnf downloads. Check which build you got:

rpm -q nushell

On our Fedora 45 VM with SELinux enforcing, it reported the Gemfury build:

nushell-0.116.1-0.x86_64

Unlike the .deb, the RPM doesn’t add /usr/bin/nu to /etc/shells. That only matters if you plan to make Nu your login shell, so the login shell section below covers the step for Fedora readers.

Install Nushell from the release tarball

The GitHub release ships static musl builds, so one tarball runs on any glibc or musl distribution without extra libraries. The Gemfury packages use the same musl build, which is why version reports x86_64-unknown-linux-musl either way. Look up the latest tag first, so the commands keep working when the next release lands:

NU_VER=$(curl -s https://api.github.com/repos/nushell/nushell/releases/latest | grep -Po '"tag_name": *"\K[^"]+')
NU_PKG="nu-${NU_VER}-$(uname -m)-unknown-linux-musl"
echo "$NU_PKG"

On an x86_64 machine the name resolves like this, and uname -m gives aarch64 on ARM servers, which matches the release asset names too:

nu-0.116.1-x86_64-unknown-linux-musl

Download the tarball and the checksum file, and verify the archive before you unpack it:

curl -sLO "https://github.com/nushell/nushell/releases/download/${NU_VER}/${NU_PKG}.tar.gz"
curl -sLO "https://github.com/nushell/nushell/releases/download/${NU_VER}/SHA256SUMS"
sha256sum --ignore-missing -c SHA256SUMS

Only the file you downloaded is checked, and it must say OK.

nu-0.116.1-x86_64-unknown-linux-musl.tar.gz: OK

Extract it and install the binary into /usr/local/bin:

tar xzf "${NU_PKG}.tar.gz"
sudo install -m 755 "${NU_PKG}/nu" /usr/local/bin/nu

The archive also holds plugin binaries. Five of them are the ones the packages ship (nu_plugin_formats, nu_plugin_gstat, nu_plugin_inc, nu_plugin_polars and nu_plugin_query), and the rest are developer examples and test plugins you don’t want registered. Copy only those five next to nu if you need extra file formats or dataframes, and register them with plugin add as the plugins section shows. To update later, run the same four steps again.

Start Nushell and set up the config

Type nu in any shell, from any terminal (we used Kitty on the desktop and plain SSH on the servers). The first interactive start creates two files in ~/.config/nushell and prints a welcome banner:

Environment config file created at: /home/josphat/.config/nushell/env.nu
Config file created at: /home/josphat/.config/nushell/config.nu

Both files are almost empty, because since 0.101 the defaults live inside Nushell itself and your config.nu only holds what you change. Older guides that tell you to copy a full default config and edit a giant $env.config = {...} record predate that. env.nu still loads, but the docs now put everything in config.nu. The quickest way to open it is config nu from inside Nu, which works wherever your config lives. On Linux the file is in ~/.config/nushell, so you can also open it directly:

vim ~/.config/nushell/config.nu

Append the settings most people change on day one. This block turns off the banner and sets the editor that config nu opens. It also switches history to SQLite, which records the directory and exit status of each command, adds ~/.local/bin to the PATH, and defines an alias and a custom command:

$env.config.show_banner = false
$env.config.buffer_editor = "vim"
$env.config.history.file_format = "sqlite"

use std/util "path add"
path add "~/.local/bin"

alias ll = ls -l

def big-files [min: filesize = 10MB] {
    ls **/* | where type == file and size > $min | sort-by size --reverse
}

Start a new nu session to load it. The big-files command walks the current directory tree and returns a real table, so you can keep piping it into where or first. In a test directory it found the three files over 10 MB:

~/demo> big-files
╭───┬──────────────────┬──────┬─────────┬──────────╮
│ # │       name       │ type │  size   │ modified │
├───┼──────────────────┼──────┼─────────┼──────────┤
│ 0 │ big.bin          │ file │ 31.4 MB │ now      │
│ 1 │ nested/inner.bin │ file │ 15.7 MB │ now      │
│ 2 │ sub.bin          │ file │ 12.5 MB │ now      │
╰───┴──────────────────┴──────┴─────────┴──────────╯

The sizes show in decimal units: big.bin was created as 30 MiB and shows as 31.4 MB. Nushell displays metric units by default, and 10MB in a filter means 10,000,000 bytes. Write 10MiB when you mean the binary size.

Work with files, processes and system data

Every built-in command returns structured data, so filtering is a column comparison rather than a regex over text. Sizes, dates and numbers keep their types through the pipe, which is why where size > 20MB compares bytes instead of strings. The output below is from the Fedora 45 VM. Find the largest binaries in a directory:

ls /usr/bin | where size > 20MB | sort-by size --reverse | first 5

On the Fedora VM only two files cleared 20 MB, and nu itself was the biggest:

╭───┬─────────────────┬──────┬─────────┬─────────────╮
│ # │      name       │ type │  size   │  modified   │
├───┼─────────────────┼──────┼─────────┼─────────────┤
│ 0 │ /usr/bin/nu     │ file │ 75.0 MB │ 2 weeks ago │
│ 1 │ /usr/bin/podman │ file │ 45.7 MB │ 5 days ago  │
╰───┴─────────────────┴──────┴─────────┴─────────────╯

Processes work the same way, and select trims the table to the columns you care about, so you skip reading a wide ps aux line by line. Sort by memory:

ps | sort-by mem --reverse | first 5 | select pid name cpu mem

On a quiet VM, the shell running the query tops the list at about 26 MB of resident memory:

╭───┬──────┬─────────────────┬──────┬─────────╮
│ # │ pid  │      name       │ cpu  │   mem   │
├───┼──────┼─────────────────┼──────┼─────────┤
│ 0 │ 6014 │ nu              │ 8.26 │ 25.9 MB │
│ 1 │  671 │ NetworkManager  │ 0.00 │ 22.6 MB │
│ 2 │    1 │ systemd         │ 0.00 │ 20.8 MB │
│ 3 │ 5410 │ systemd-journal │ 0.00 │ 17.6 MB │
│ 4 │ 5964 │ systemd         │ 0.00 │ 14.9 MB │
╰───┴──────┴─────────────────┴──────┴─────────╯

The same three kinds of query, run back to back on the Fedora VM, look like this in a terminal:

Nushell sys host, ls where size and ps sort-by mem tables on Fedora 45

System details live under the sys subcommands, which replaced the old single sys table:

sys host | select name os_version kernel_version hostname

A one-row record prints as a two-column key and value table.

╭────────────────┬───────────────────────╮
│ name           │ Fedora Linux          │
│ os_version     │ 45                    │
│ kernel_version │ 7.2.7-300.fc45.x86_64 │
│ hostname       │ f45-01                │
╰────────────────┴───────────────────────╯

Text files and the output of external tools turn into tables with lines, split column and detect columns. This counts which login shells the accounts in /etc/passwd use:

open /etc/passwd | lines | split column ":" user pw uid gid comment home shell | get shell | uniq --count | sort-by count --reverse

Most system accounts have nologin, and only three can log in with Bash:

╭───┬───────────────────┬───────╮
│ # │       value       │ count │
├───┼───────────────────┼───────┤
│ 0 │ /usr/sbin/nologin │    19 │
│ 1 │ /sbin/nologin     │     3 │
│ 2 │ /bin/bash         │     3 │
│ 3 │ /sbin/halt        │     1 │
│ 4 │ /sbin/shutdown    │     1 │
│ 5 │ /bin/sync         │     1 │
╰───┴───────────────────┴───────╯

For df, add --guess to detect columns. Without it, the header Mounted on splits into two columns and where Mounted == "/" fails with Cannot find column 'Mounted':

df -h | detect columns --guess | where "Mounted on" == "/"

With the guess mode, the header stays intact and the filter matches the root filesystem:

╭───┬────────────┬──────┬──────┬───────┬──────┬────────────╮
│ # │ Filesystem │ Size │ Used │ Avail │ Use% │ Mounted on │
├───┼────────────┼──────┼──────┼───────┼──────┼────────────┤
│ 0 │ /dev/sda3  │ 20G  │ 1.4G │ 19G   │ 7%   │ /          │
╰───┴────────────┴──────┴──────┴───────┴──────┴────────────╯

Prefix an external program with ^ when a Nu built-in has the same name, for example ^ls -la runs GNU ls instead of Nu’s.

Query CSV, JSON and YAML files

open reads a file by its extension and hands back a table, which is where Nushell earns its place next to jq and yq. One query language then covers CSV, JSON, YAML and TOML, so you stop switching tools per format. Create a small inventory to try it on:

vim servers.csv

Paste these rows and save.

name,role,cpu,ram_gb
web01,nginx,4,8
web02,nginx,4,8
db01,postgres,8,32
cache01,redis,2,4

Nushell infers the column types, so numeric comparisons and sorting work straight away:

open servers.csv | where ram_gb > 4 | sort-by ram_gb --reverse

The 4 GB cache host drops out and the database server sorts to the top:

╭───┬───────┬──────────┬─────┬────────╮
│ # │ name  │   role   │ cpu │ ram_gb │
├───┼───────┼──────────┼─────┼────────┤
│ 0 │ db01  │ postgres │   8 │     32 │
│ 1 │ web02 │ nginx    │   4 │      8 │
│ 2 │ web01 │ nginx    │   4 │      8 │
╰───┴───────┴──────────┴─────┴────────╯

Run open servers.csv | describe to see the inferred types, which came back as table<name: string, role: string, cpu: int, ram_gb: int>. Aggregates take one more pipe, and open servers.csv | get ram_gb | math sum returned 52. Converting between formats is a pipe too. Save the table as JSON, then read it back, double the RAM on the Nginx hosts and print the result as YAML:

open servers.csv | to json | save -f servers.json
open servers.json | where role == nginx | update ram_gb {|r| $r.ram_gb * 2 } | to yaml

Only the two web servers come through, each with the new value.

- name: web01
  role: nginx
  cpu: 4
  ram_gb: 16
- name: web02
  role: nginx
  cpu: 4
  ram_gb: 16

Remote JSON works the same way through http get, which parses the response for you. http get https://api.github.com/repos/nushell/nushell/releases/latest | select tag_name published_at returned the current tag and its publish date as a two-row record, with no jq involved.

Register the bundled plugins

The packages install five plugins under /usr/libexec/nushell and register them during install, but they register them for root only. As a normal user on Ubuntu 24.04 we got empty list from plugin list, while sudo nu -c "plugin list" showed all five loaded. Register them for your own account:

nu -c 'ls /usr/libexec/nushell/nu_plugin_* | each {|p| plugin add $p.name } | ignore'

Open a new Nu session and check the list with plugin list | select name version status. All five should show as loaded:

╭───┬─────────┬─────────┬────────╮
│ # │  name   │ version │ status │
├───┼─────────┼─────────┼────────┤
│ 0 │ formats │ 0.116.1 │ loaded │
│ 1 │ gstat   │ 0.116.1 │ loaded │
│ 2 │ inc     │ 0.116.1 │ loaded │
│ 3 │ polars  │ 0.116.1 │ loaded │
│ 4 │ query   │ 0.116.1 │ loaded │
╰───┴─────────┴─────────┴────────╯

formats adds readers such as from ini, from eml and from ics, gstat summarises a Git repository, and polars gives you dataframes for files too big for a plain table. Plugins are tied to the Nu version, so run the same one-liner again after each apt upgrade or dnf upgrade that brings a new release, because the package only re-registers them for root. For a tarball install, point the command at the directory you copied the five plugin binaries into.

Make Nu your login shell

Try Nu for a few days from inside your current shell before you switch. It is not POSIX, so a script or tool that assumes it can run sh syntax in your login shell will break. Scripts with a #!/bin/bash line are safe, because the kernel starts the interpreter named on that line, and the trouble is limited to commands typed into the login shell or sent to it over SSH.

Keep your current SSH session open until you have tested a fresh login from a second terminal, so a mistake never leaves you without a working shell. chsh expects the shell to be listed in /etc/shells. The .deb adds it for you; on Fedora, or for a tarball install, add the path that command -v nu reports. Run this command and the next one from Bash, not from a Nu prompt, because they use POSIX syntax that Nu doesn’t understand:

command -v nu | sudo tee -a /etc/shells

Change the shell with sudo. A plain chsh asks for your account password, and on cloud images where the password is locked and you log in with a key, it fails with chsh: Authentication failure, which is what our Fedora 45 VM returned:

sudo chsh -s "$(command -v nu)" "$USER"

After we switched the josphat account on Fedora 45, this is what changed for that account:

  • New SSH logins started in Nu
  • Commands passed over SSH ran through Nu as well
  • scp kept working, because sshd launches the SFTP server through nu -c and Nu starts it without printing anything

The risk is any automation that sends POSIX syntax over SSH, because Nu now parses the remote command. We found out the hard way: a Bash loop we sent to the Fedora box over SSH, while its account still had Nu as the login shell, died on the first redirection:

Error: nu::parser::shell_outerr

  x The '2>&1' shell operation is 'out+err>' in Nushell.
  help: Nushell redirection will write all of stdout before stderr.

Wrap such commands in bash -c '...', or better, keep Ansible and other automation users on Bash and switch only your own interactive account.

A syntax error in config.nu doesn’t lock you out. When we broke the file on purpose, the next login printed nu::parser::unclosed_delimiter with the line number and then started Nu with its defaults. If you would rather fix things from Bash, ssh -t user@host bash gives you one, because Nu runs that command string without loading your config. To go back for good from a Nu prompt, run sudo chsh -s /bin/bash $env.USER. Nu doesn’t expand "$USER" inside plain double quotes, so the Bash form fails there with chsh: user "$USER" does not exist., which is what we got when we tried it. The one case these don’t cover is a missing nu binary, for example after removing the package while it is still your login shell, which needs root or console access to undo.

Notes for macOS

We did not test macOS for this guide, but three things differ there according to the Nushell docs, and the last two break a Linux step if you copy it as-is:

  • Install with brew install nushell
  • The config lives in ~/Library/Application Support/nushell, so open it with config nu rather than the Linux path
  • Homebrew puts nu under /opt/homebrew/bin on Apple Silicon, so use command -v nu for the /etc/shells and chsh lines

Nu’s own open also hides the macOS open command. The docs fix it with two aliases in config.nu, in this order: alias nu-open = open, then alias open = ^open.

Fix old Nushell commands that no longer work

Most broken Nushell snippets come from tutorials written before a rename, so the error usually names a command that still exists under another name. This table maps the ones we hit on the current release, and the sections after it show the exact errors:

Old formCurrent formWhat happens on the old form
fetch URLhttp get URLError: removed command
let-env FOO = "x"$env.FOO = "x"Parse error
syssys host, sys mem and the other subcommandsPrints help only
$nu.home-path, $nu.temp-path$nu.home-dir, $nu.temp-dirColumn not found
str upcase, str downcasestr uppercase, str lowercaseWorks with a deprecation warning

Fix the deprecation warnings too, because each one turns into an error once the old name is removed in a later release.

Error: Removed command: fetch

fetch became http get, and Nu now says so in the error:

Error: nu::shell::removed_command

  x Removed command: fetch
   ,-[source:1:1]
 1 | fetch https://api.github.com/repos/nushell/nushell/releases/latest
   : ^^|^^
   :   `-- 'fetch' has been removed from Nushell. Please use 'http get' instead.

Replace fetch with http get. The flags for headers and authentication moved with it, so check help http get for the current names.

Error: Assignment operations require a variable (let-env)

let-env EDITOR = "vim" fails with nu::parser::assignment_requires_variable, because let-env was removed. Assign to the environment directly with $env.EDITOR = "vim", which is also the form to use in config.nu.

sys prints help instead of a table

Bare sys now only prints You must use one of the following subcommands. Use sys host, sys cpu, sys mem, sys disks, sys net, sys temp or sys users for the part you need.

Cannot find column ‘home-path’

Paths on the $nu record were renamed, so $nu.home-path now fails with nu::shell::column_not_found. Use $nu.home-dir, and $nu.temp-dir in place of $nu.temp-path.

Warning: str upcase was deprecated

Some commands still run but print a deprecation warning first. "hello" | str upcase returned HELLO after a warning that names the replacement, str uppercase, and str downcase became str lowercase the same way. Fix these before they become errors in a later release. Nushell publishes a post for each release on its blog with every breaking change, and with a release every six weeks it’s worth a skim before each upgrade.

If you came to Nu from Zsh, the Zsh and Oh My Zsh setup covers the shell you’re comparing it against, and our roundup of Linux and macOS shells puts Nu next to Fish and the rest. The grep, awk and sed comparison shows the text tools that Nu’s tables replace, which is handy when you still need them on a box without Nu.

Keep reading

macOS Keyboard Shortcuts Cheat Sheet (Golden Gate and Tahoe) Cheat Sheets macOS Keyboard Shortcuts Cheat Sheet (Golden Gate and Tahoe) Fix “apt-key is deprecated. Manage keyring files in trusted.gpg.d instead” Debian Fix “apt-key is deprecated. Manage keyring files in trusted.gpg.d instead” Best Terminal Shell Prompts for Zsh, Bash and Fish Featured Best Terminal Shell Prompts for Zsh, Bash and Fish EC2 Mac Instance on AWS: Run mac-m4.metal for Just 24 Hours AWS EC2 Mac Instance on AWS: Run mac-m4.metal for Just 24 Hours Best USB-C Hub and Thunderbolt Dock for MacBook Air and Pro macos Best USB-C Hub and Thunderbolt Dock for MacBook Air and Pro Best Media Player for macOS Elmedia Player macos Best Media Player for macOS Elmedia Player

2 thoughts on “Install and Use Nu Shell (Nushell) on Linux and macOS”

    • Nu Shell does not auto-start ssh-agent the way bash or zsh do with profile scripts. You can add this to your Nu config (~/.config/nushell/config.nu):

      if (pgrep ssh-agent | is-empty) { ssh-agent -s | lines | each { |line| let parts = ($line | split column "=" | get column1 column2); load-env {($parts.0): $parts.1} } }

      Or the simpler approach: start ssh-agent in your login shell (bash/zsh) before launching Nu, and the environment variables will carry over.

      Reply

Leave a Comment

Press ESC to close