You pipe JSON from an API into grep, then cut, then awk, and the script breaks the day a field moves. Nushell skips the text parsing. Every command hands the next one a table, a list, or a record, so you filter and sort by column name instead of by character position.

This guide covers the 20% of Nushell that does 80% of the work: tables and pipelines, opening data files, records and lists, finding help, variables, custom commands with def, and your configuration file. A searchable command reference and a curated set of videos, podcasts, books, and online resources close the article.

What You’ll Learn

  • When Nushell is the right tool, and when bash or zsh still fits better.
  • How tables flow through a pipeline, so where, select, and sort-by make sense.
  • How to open JSON, CSV, and TOML files and convert between formats.
  • How records and lists work, and how to read a field.
  • How to find help, store values in variables, and write your own commands.
  • How to find and change your Nushell configuration.

When to Use Nushell

Nushell (often called nu) is a cross-platform shell that treats output as structured data. If you want the background on why it exists, read What Is Nushell? first. This guide sticks to the commands.

What Nushell Does Well

  • Exploring JSON, CSV, TOML, and YAML files without a separate tool for each format.
  • Filtering and sorting command output by column, such as files by size.
  • Writing small scripts with typed parameters and clear error messages.
  • Working the same way on Linux, macOS, and Windows.

Where Nushell Is Not the Right Tool

  • Running existing bash scripts. Nushell is not POSIX compatible, so .sh files still need bash.
  • Pasting one-liners from bash tutorials. Syntax such as $(...), && chains, and export VAR=value differs.
  • Shared team scripts, when the rest of the team doesn’t run Nushell.

Install Nushell with your package manager, then run nu to start it:

# macOS
brew install nushell

# Windows
winget install nushell

# Any platform with Rust installed
cargo install nu --locked

The examples below use a small practice file, servers.json, holding five servers with a name, region, status, and CPU percentage. The Practice Nushell section shows how to create it in your own terminal.

Tables and Pipelines

Most Nushell commands return a table. Open the practice file and you see rows and named columns instead of raw text:

open servers.json
╭───┬──────────┬────────────┬─────────┬─────╮
│ # │   name   │   region   │ status  │ cpu │
├───┼──────────┼────────────┼─────────┼─────┤
│ 0 │ web-01   │ us-west    │ running │  42 │
│ 1 │ web-02   │ us-west    │ stopped │   0 │
│ 2 │ db-01    │ us-east    │ running │  71 │
│ 3 │ cache-01 │ eu-central │ running │  18 │
│ 4 │ batch-01 │ us-east    │ stopped │   0 │
╰───┴──────────┴────────────┴─────────┴─────╯

The pipe | passes that table to the next command. Use where to keep matching rows and select to keep only the columns you need:

open servers.json | where status == "running" | select name cpu
╭───┬──────────┬─────╮
│ # │   name   │ cpu │
├───┼──────────┼─────┤
│ 0 │ web-01   │  42 │
│ 1 │ db-01    │  71 │
│ 2 │ cache-01 │  18 │
╰───┴──────────┴─────╯

Sort by any column with sort-by, and take the top rows with first:

open servers.json | sort-by cpu --reverse | first 2
╭───┬────────┬─────────┬─────────┬─────╮
│ # │  name  │ region  │ status  │ cpu │
├───┼────────┼─────────┼─────────┼─────┤
│ 0 │ db-01  │ us-east │ running │  71 │
│ 1 │ web-01 │ us-west │ running │  42 │
╰───┴────────┴─────────┴─────────┴─────╯

Built-in commands work the same way. ls returns a table of files, so you filter it by size with no awk in sight:

ls | where size > 4kb | get name
╭───┬────────────╮
│ 0 │ access.log │
╰───┴────────────╯

What you get: one habit, command | where ... | select ..., that works on files, processes, and data files alike.

Open Data Files

open reads a file and parses it by its extension. JSON, CSV, TOML, YAML, and more come back as tables and records:

open app.toml | get server.port
8080
open app.toml | get log.level
info

CSV files open as tables too, so the same commands apply:

open sales.csv | first 3
╭───┬────────────┬────────────┬─────────┬───────┬────────╮
│ # │    date    │   region   │ product │ units │ amount │
├───┼────────────┼────────────┼─────────┼───────┼────────┤
│ 0 │ 2026-03-01 │ us-west    │ gadget  │    19 │ 228.00 │
│ 1 │ 2026-03-01 │ us-east    │ widget  │    35 │ 665.00 │
│ 2 │ 2026-03-02 │ eu-central │ gizmo   │    36 │ 684.00 │
╰───┴────────────┴────────────┴─────────┴───────┴────────╯

Count rows with length:

open sales.csv | where region == "eu-central" | length
20

Group rows by a column with group-by. The result is a record keyed by the column’s values, so columns lists the groups:

open servers.json | group-by region | columns
╭───┬────────────╮
│ 0 │ us-west    │
│ 1 │ us-east    │
│ 2 │ eu-central │
╰───┴────────────╯

Reduce a column to one number with the math commands:

open sales.csv | get units | math sum
1317
open servers.json | get cpu | math max
71

Files that Nushell doesn’t recognize, such as a log, open as text. Split text into a list of lines with lines:

open access.log | lines | first 2
╭───┬──────────────────────────────────────────────────────────────────────────╮
│ 0 │ 10.0.0.222 - - [09/Jan/2026:08:00:00 +0000] "GET /api/servers HTTP/1.1"  │
│   │ 500 5534                                                                 │
│ 1 │ 10.0.0.156 - - [09/Jan/2026:08:00:01 +0000] "GET /api/sales HTTP/1.1"    │
│   │ 500 4982                                                                 │
╰───┴──────────────────────────────────────────────────────────────────────────╯

A list of lines works with the same commands as any other list. Count the lines, then count the requests that got a 404:

open access.log | lines | length
190
open access.log | lines | where $it =~ " 404 " | length
17

Add --raw to skip parsing and get the file’s text, even for a format Nushell knows, such as Markdown:

open --raw notes.md | lines | first
# Practice notes

Convert between formats with the to commands, and write the result with save:

open servers.json | where status == "stopped" | to csv
name,region,status,cpu
web-02,us-west,stopped,0
batch-01,us-east,stopped,0
open servers.json | where status == "running" | save --force running.json

save picks the format from the file extension, so running.json gets JSON. --force replaces the file if it already exists. The from commands go the other way: they parse text you already have, such as the body of a web response, into a table or record.

If you know jq, where status == "running" does the job of jq’s select(.status == "running"). The Learn jq cookbook shows the jq side of the same structured-data ideas.

Records and Lists

A table is a list of records. A record is a set of named fields in braces, and a list is values in square brackets. Read a field with a dot:

let server = {name: "web-01", region: "us-west", cpu: 42}
$server.cpu
42
let regions = [us-west us-east eu-central]
$regions | length
3

get pulls one column out of a table as a list, or one row out as a record:

open servers.json | get name
╭───┬──────────╮
│ 0 │ web-01   │
│ 1 │ web-02   │
│ 2 │ db-01    │
│ 3 │ cache-01 │
│ 4 │ batch-01 │
╰───┴──────────╯

When a command does something unexpected, ask what kind of value you have with describe:

let server = {name: "web-01", region: "us-west", cpu: 42}
$server | describe
record<name: string, region: string, cpu: int>

Get Help

Nushell documents itself. Run help with a command name to see its description, flags, and examples:

help where

help commands lists every command as a table, so you search it with the same where you use on data:

help commands | where name =~ "sort" | get name
╭───┬─────────╮
│ 0 │ sort    │
│ 1 │ sort-by │
╰───┴─────────╯

help --find csv searches command descriptions as well as names, which helps when you know what you want to do but not the command’s name.

Variables

let creates a variable you can’t change. mut creates one you can. Prefix a variable with $ to read it:

mut total = 0
for s in (open servers.json) { $total += $s.cpu }
$total
131

Use a variable anywhere a value fits, such as a filter:

let threshold = 40
open servers.json | where cpu > $threshold | length
2

Reach for let first. Most of the time a pipeline gives you the value directly, and mut is only needed for loops like the one above.

Write Your Own Commands

def defines a command. Parameters can carry a type and a default value, and the command behaves like a built-in one, help text included:

def busy [threshold: int = 40] { open servers.json | where cpu > $threshold | get name }
busy
busy 60
╭───┬───────╮
│ 0 │ db-01 │
╰───┴───────╯

busy with no argument uses the default of 40 and lists web-01 and db-01. busy 60 lists only db-01, as shown. Commands you define at the prompt last until you close the shell. Put them in your configuration file to keep them.

Configure Nushell

Nushell keeps its configuration in config.nu. Print its location with $nu.config-path, and open it in your editor with config nu:

$nu.config-path

The practice shell prints this path. On your machine, it starts with your own home directory:

/home/reader/.config/nushell/config.nu

Settings live in the $env.config record. Change one for the current session by assigning to it:

$env.config.table.mode = "light"
open servers.json | first 2
 #    name    region    status    cpu
──────────────────────────────────────
 0   web-01   us-west   running    42
 1   web-02   us-west   stopped     0

To keep a setting, run config nu and add the same line, $env.config.table.mode = "light", to the end of config.nu.

Changes to config.nu apply to new sessions. Run nu to start a fresh session inside the current one, check the change, then run exit to return.

Practice Nushell

Nushell sticks once you type it. A practice shell that runs real Nushell in this page is on its way to this section. Until it arrives, run the drill below in your own terminal. It takes about ten minutes.

First, create the practice file in an empty directory. This one command builds a table from literal rows and saves it as JSON, unless the file is already there:

if not ("servers.json" | path exists) { [[name region status cpu]; [web-01 us-west running 42] [web-02 us-west stopped 0] [db-01 us-east running 71] [cache-01 eu-central running 18] [batch-01 us-east stopped 0]] | save servers.json }

Then work through these steps:

  1. Run open servers.json and read the table.
  2. List the stopped servers with where status == "stopped".
  3. Show only the name and region columns of the running servers.
  4. Sort the servers by cpu, highest first, and keep the top three.
  5. Count the servers in us-east.
  6. Convert the running servers to CSV with to csv.
  7. Define a command named in-region that takes a region and lists the server names in it, then run in-region eu-central.

Command Reference

Verified with Nushell 0.116.0. Search the reference to find a command fast. Each entry shows the command, then what it does.

Filter and Shape Tables

  • where status == “running” - Keep the rows that match a condition
  • where cpu > 40 - Keep the rows where a number column is above a value
  • select name cpu - Keep only the named columns
  • get name - Pull one column out of a table as a list
  • sort-by cpu - Sort rows by a column, lowest first
  • sort-by cpu –reverse - Sort rows by a column, highest first
  • first 2 - Keep the first rows of a table or list
  • length - Count the rows of a table or the items of a list
  • group-by region - Group rows into a record keyed by a column’s values
  • columns - List the column names of a table

Files and Formats

  • open servers.json - Read a file and parse it by its extension
  • open access.log | lines - Read a text file as a list of lines
  • ls - List the files in the current directory as a table
  • save running.json - Write the pipeline’s value to a file, in the format the extension names
  • save –append notes.md - Add the pipeline’s value to the end of a file
  • to csv - Convert a table to CSV text
  • to json - Convert a value to JSON text
  • to nuon - Convert a value to NUON, Nushell’s own data format
  • from json - Parse JSON text into a table or record
  • from csv - Parse CSV text into a table

Records, Lists, and Types

  • {name: “web-01”, cpu: 42} - Create a record with named fields
  • [3 1 2] - Create a list of values
  • $server.cpu - Read a field from a record
  • $regions | length - Count the items in a list
  • describe - Show the type of a value

Help

  • help where - Show a command’s description, flags, and examples
  • help commands - List every command as a table you can filter
  • help –find csv - Search command names and descriptions for a word

Variables and Commands

  • let name = value - Create a variable you can’t change
  • mut total = 0 - Create a variable you can change
  • def busy [threshold: int = 40] { … } - Define a command with a typed parameter and a default

Configuration

  • $nu.config-path - Print the path of your config.nu file
  • config nu - Open config.nu in your editor
  • $env.config - The record that holds every setting
  • $env.config.table.mode = “light” - Change a setting for the current session

Examples

Each example runs on the practice file and shows what Nushell prints.

open servers.json | where region == "us-east" | get name
╭───┬──────────╮
│ 0 │ db-01    │
│ 1 │ batch-01 │
╰───┴──────────╯
open servers.json | first | to nuon
{name: "web-01", region: us-west, status: running, cpu: 42}
open servers.json | group-by status
╭─────────┬────────────────╮
│ running │ [table 3 rows] │
│ stopped │ [table 2 rows] │
╰─────────┴────────────────╯

Plugins and Networking

Nushell fetches web data with http get, and the response arrives already parsed. This example needs a network connection:

http get https://api.github.com/repos/nushell/nushell | get stargazers_count # runs on your machine

Plugins add commands written in Rust or other languages. The Polars plugin adds fast dataframe commands for large CSV and Parquet files. Install it with Cargo, then register it with Nushell:

cargo install nu_plugin_polars --locked
plugin add ~/.cargo/bin/nu_plugin_polars # runs on your machine
plugin use polars # runs on your machine

Run external programs as you would in any shell. When an external program shares a name with a Nushell command, prefix it with ^ to run the program instead:

^git status # runs on your machine

Learn Nushell: Beyond the Basics

With pipelines, data files, and a custom command or two in hand, these resources take you further. Start with a video, then keep the Nushell Book open while you write your first scripts.

Video

Audio and Podcasts

  • Nushell for the GitHub era on The Changelog, with creators Jonathan Turner, Andrés N. Robalino, and Yehuda Katz on why they built it.
  • How to build a Nushell on Ship It!, with core maintainers Devyn Cairns and Jakub Žádník on building a cross-platform shell.
  • Nushell with WindSoilder on Rustacean Station, a conversation with a Nushell contributor about the project’s Rust internals.

Books

Online

  • What Is Nushell? explains why a shell built on structured data exists and when it fits.
  • Learn jq covers the JSON filter many people use before they try Nushell.
  • jq vs. yq compares two single-format tools that Nushell’s open command replaces for many tasks.
  • Learn Tmux pairs with Nushell: run nu in one pane and your editor in another.

Related Articles by Category