Structures#

Nushell has real types, not strings-pretending-to-be-numbers. The everyday types are scalars (int, float, string, bool, date, duration, filesize), lists, records (key / value maps with typed values), tables (lists of records with the same schema), binary (raw bytes), and closures. Everything that pipelines carry is one of these types; describe reveals which one.

Types#

Type

Literal

When to reach for it

int

42

Whole numbers. 64-bit.

float

3.14

Decimal values. IEEE-754 double.

string

"text" / 'text'

Text. Single quotes never expand.

bool

true / false

Boolean.

date

2026-05-15, date now

Instant in time, timezone-aware.

duration

5min, 2hr, 7day

Span of time. Arithmetic with date.

filesize

1KiB, 2GB

Size in bytes; arithmetic respects the unit.

list

[1 2 3]

Ordered, possibly heterogeneous collection.

record

{name: "x", age: 1}

Key / value map, typed per field.

table

[[a b]; [1 2] [3 4]]

List of records sharing a schema.

binary

0x[ff ee dd]

Raw bytes.

closure

{|x| $x + 1}

First-class callable; each, where, reduce accept one.

cellpath

$.users.0.name

Path expression that walks records / tables.

Inspect a value’s type.

42 | describe                                # int
{a: 1, b: 2} | describe                      # record<a: int, b: int>
ls | describe                                # table<name: string, type: string, size: filesize, modified: datetime>

Scalars#

Real types and real arithmetic.

2 + 3                                        # 5
3.14 * 2                                     # 6.28
"hello, " + $name                            # concatenation
2026-05-15 + 7day                            # 2026-05-22
1KiB + 512B                                  # 1.5 KiB
30min < 1hr                                  # true

Casts use the into family.

"42" | into int                              # 42
42 | into string                             # "42"
"2026-05-15" | into datetime
42 | into filesize                           # 42 B

List#

Ordered collection, indexed from 0, allowed to hold mixed types but usually homogeneous in practice.

let xs = [1 2 3 4 5]
$xs.0                                        # 1
$xs | length                                 # 5
$xs | append 6                               # [1 2 3 4 5 6]
$xs | prepend 0                              # [0 1 2 3 4 5]
$xs | first 3                                # [1 2 3]
$xs | last 2                                 # [4 5]
$xs | reverse                                # [5 4 3 2 1]
$xs | range 1..3                             # [2 3 4]
$xs | where $it > 2                          # [3 4 5]
$xs | each { |n| $n * 2 }                    # [2 4 6 8 10]

Record#

Key / value map with typed values. Keys are strings; iteration order is insertion order.

let user = {name: "operator", age: 36, role: "analyst"}
$user.name                                   # operator
$user | get name                             # same; via get
$user | columns                              # [name age role]
$user | values                               # [operator 36 analyst]
$user | upsert email "op@example.com"        # add or replace a field
$user | reject role                          # drop a field
$user | items {|k, v| [$k $v] }              # iterate as pairs

Table#

A list of records sharing a schema. The default rendering of multi-row data. Every Nu command that produces “rows” emits a table.

let users = [
    {name: "alice", age: 30, region: "us-east-1"}
    {name: "bob",   age: 25, region: "eu-west-1"}
    {name: "carol", age: 40, region: "us-east-1"}
]

$users | where region == "us-east-1"
$users | sort-by age
$users | sort-by age --reverse
$users | group-by region
$users | select name age
$users | reject region
$users | first 2

The same table form on the wire as a literal.

let users = [[name age region];
             [alice 30 us-east-1]
             [bob 25 eu-west-1]
             [carol 40 us-east-1]]

Picking the right type#

Type

Reach for it when…

Watch out for

Scalar

Single value, single type.

The everyday case; no surprises.

List

Ordered values, possibly mixed type.

Most pipelines emit a list of records (i.e. a table); plain lists are less common than they look.

Record

One row of named fields.

Equivalent of a PSCustomObject or a JSON object; the default “one item” structure.

Table

Many rows, same schema.

Mismatch in schema between rows turns the table into a heterogeneous list; select first.

Closure

Callable passed to a higher-order command.

Captures variables by reference; mind lifetimes.

Hosts#

The everyday list structure.

let hosts = [web01 web02 db01 cache01]
$hosts | each { |h|
    ^ssh -o ConnectTimeout=3 $h "uptime" | str trim
}

Drive from a file.

let hosts = (open hosts.txt | lines)
let hosts = (open inventory.txt
    | lines
    | where $it =~ '^prod-'
    | each { |line| $line | split row " " | first })

Host → metadata#

The record / table structure that replaces an associative array.

let region = {
    web01:   "us-east-1"
    db01:    "eu-west-1"
    cache01: "us-west-2"
}
let h = "web01"
$region | get $h                             # us-east-1
^aws --region ($region | get $h) ec2 describe-instances

Record per host#

Nu’s natural structure. A table is already “record per host”.

let hosts = [
    {host: web01,   region: us-east-1, port: 443}
    {host: db01,    region: eu-west-1, port: 5432}
    {host: cache01, region: us-west-2, port: 6379}
]

$hosts | where port > 1000
$hosts | select host port

Sets via lists#

There is no set type. uniq and in cover the everyday need.

open access.log | lines | each { |l| $l | split row " " | first } | uniq

if "192.0.2.1" in $ips { "known" } else { "new" }

References#