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 |
|
Whole numbers. 64-bit. |
float |
|
Decimal values. IEEE-754 double. |
string |
|
Text. Single quotes never expand. |
bool |
|
Boolean. |
date |
|
Instant in time, timezone-aware. |
duration |
|
Span of time. Arithmetic with date. |
filesize |
|
Size in bytes; arithmetic respects the unit. |
list |
|
Ordered, possibly heterogeneous collection. |
record |
|
Key / value map, typed per field. |
table |
|
List of records sharing a schema. |
binary |
|
Raw bytes. |
closure |
|
First-class callable; |
cellpath |
|
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 |
Table |
Many rows, same schema. |
Mismatch in schema between rows turns the table
into a heterogeneous list; |
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#
Overview for the language-level type and operator surface.
I/O and Pipelines for how these structures flow through pipelines and across the host-shell boundary.
Algorithms for table operations (sort, group, join, aggregate).