Nushell Usage Patterns Critical Distinctions Pipeline Input vs Parameters
CRITICAL: Pipeline input ($in) is NOT interchangeable with function parameters!
❌ WRONG - treats $in as first parameter
def my-func [list: list, value: any] { $list | append $value }
✅ CORRECT - declares pipeline signature
def my-func [value: any]: list -> list { $in | append $value }
Usage
[1 2 3] | my-func 4 # Works correctly my-func [1 2 3] 4 # ERROR! my-func doesn't take positional params
This applies to closures too.
Why this matters:
Pipeline input can be lazily evaluated (streaming) Parameters are eagerly evaluated (loaded into memory) Different calling conventions entirely Type Signatures
No pipeline input
def func [x: int] { ... } # (x) -> output
Pipeline input only
def func []: string -> int { ... } # string | func -> int
Both pipeline and parameters
def func [x: int]: string -> int { ... } # string | func x -> int
Generic pipeline
def func []: any -> any { ... } # works with any input type
Common Patterns Working with Lists
Filter with index
$list | enumerate | where {|e| $e.index > 5 and $e.item.some-bool-field}
Transform with previous state
$list | reduce --fold 0 {|item, acc| $acc + $item.value}
Working with Records
Create record
{name: "Alice", age: 30}
Merge records (right-biased)
$rec1 | merge $rec2
Merge many records (right-biased)
[$rec1 $rec2 $rec3 $rec4] | into record
Update field
$rec | update name {|r| $"Dr. ($r.name)"}
Insert field
$rec | insert active true
Insert field based on existing fields
{x:1, y: 2} | insert z {|r| $r.x + $r.y}
Upsert (update or insert)
$rec | upsert count {|r| ($r.count? | default 0) + 1}
Reject fields
$rec | reject password secret_key
Select fields
$rec | select name age email
Working with Tables
Tables are lists of records
let table = [ {name: "Alice", age: 30} {name: "Bob", age: 25} ]
Filter rows
$table | where age > 25
Add column
$table | insert retired {|row| $row.age > 65}
Rename column
$table | rename -c {age: years}
Group by
$table | group-by status --to-table
Transpose (rows ↔ columns)
$table | transpose name data
Conditional Execution
If expressions return values
let result = if $condition { "yes" } else { "no" }
Match expressions
let result = match $value { 0 => "zero" 1..10 => "small" _ => "large" }
Null Safety
Optional fields with ?
$record.field? # Returns null if missing $record.field? | default "N/A" # Provide fallback
Check existence
if ($record.field? != null) { ... }
Error Handling
Try-catch
try { dangerous-operation } catch {|err| print $"Error: ($err.msg)" }
Returning errors
def my-func [] { if $condition { error make {msg: "Something went wrong"} } else { "success" } }
Check command success
let result = try { fallible-command } if ($result == null) { # Handle error }
Use complete for detailed error info for EXTERNAL commands (bins)
let result = (fallible-external-command | complete) if $result.exit_code != 0 { print $"Error: ($result.stderr)" }
Closures and Scoping
Closures capture environment
let multiplier = 10 let double_and_add = {|x| ($x * 2) + $multiplier} 5 | do $double_and_add # Returns 20
Outer mutable variables CANNOT be captured in closures
mut sum = 0 [1 2 3] | each {|x| $sum = $sum + $x} # ❌ WON'T COMPILE
Use reduce instead
let sum = [1 2 3] | reduce {|x, acc| $acc + $x}
Iteration Patterns
each: transform each element
$list | each {|item| $item * 2}
each --flatten: stream outputs instead of collecting
Turns list> into list by streaming items as they arrive
ls *.txt | each --flatten {|f| open $f.name | lines } | find "TODO"
each --keep-empty: preserve null results
[1 2 3] | each --keep-empty {|e| if $e == 2 { "found" }}
Result: ["" "found" ""] (vs. without flag: ["found"])
filter/where: select elements
Row condition (field access auto-uses $it)
$table | where size > 100 # Implicit: $it.size > 100 $table | where type == "file" # Implicit: $it.type == "file"
Closure (must use $in or parameter)
$list | where {|x| $x > 10} $list | where {$in > 10} # Same as above
reduce/fold: accumulate
$list | reduce --fold 0 {|item, acc| $acc + $item}
Reduce without fold (first element is initial accumulator)
[1 2 3 4] | reduce {|it, acc| $acc - $it} # ((1-2)-3)-4 = -8
par-each: parallel processing
$large_list | par-each {|item| expensive-operation $item}
for loop (imperative style)
for item in $list { print $item }
String Manipulation
Interpolation
$"Hello ($name)!" $"Sum: (1 + 2)" # "Sum: 3"
Split/join
"a,b,c" | split row "," # ["a", "b", "c"] ["a", "b"] | str join ", " # "a, b"
Regex
"hello123" | parse --regex '(?P
Multi-line strings
$" Line 1 Line 2 "
Glob Patterns (File Matching)
Basic patterns
glob .rs # All .rs files in current dir glob /.rs # Recursive .rs files glob */.{rs,toml} # Multiple extensions
Note: Prefer glob over find or ls for file searches - it's more efficient and has better pattern support.
Module System
Define module
module my_module { export def public-func [] { ... } def private-func [] { ... }
export const MY_CONST = 42
}
Use module
use my_module * use my_module [public-func MY_CONST]
Import from file
use lib/helpers.nu *
Row Conditions vs Closures
Many commands accept either a row condition or a closure:
Row Conditions (Short-hand Syntax)
Automatic $it expansion on left side
$table | where size > 100 # Expands to: $it.size > 100 $table | where name =~ "test" # Expands to: $it.name =~ "test"
Works with: where, filter (DEPRECATED, use where), find, skip while, take while, etc.
ls | where type == file # Simple and readable
Limitations:
Cannot be stored in variables Only field access on left side auto-expands Subexpressions need explicit $it: ls | where ($it.name | str downcase) =~ readme # Need $it here
Closures (Full Flexibility)
Use $in or parameter name
$table | where {|row| $row.size > 100} $table | where {$in.size > 100}
Can be stored and reused
let big_files = {|row| $row.size > 1mb} ls | where $big_files
Works anywhere
$list | each {|x| $x * 2} $list | where {$in > 10}
When to use:
Row conditions: Simple field comparisons (cleaner syntax) Closures: Complex logic, reusable conditions, nested operations Common Pitfalls each on Single Records
❌ Don't pass single records to each
let record = {a: 1, b: 2} $record | each {|field| print $field} # Only runs once!
✅ Use items, values, or transpose instead
$record | items {|key, val| print $"($key): ($val)"} $record | transpose key val | each {|row| ...}
Pipe vs Call Ambiguity
These are different!
$list | my-func arg1 arg2 # $list piped, arg1 & arg2 as params my-func $list arg1 arg2 # All three as positional params (if signature allows)
Optional Fields
❌ Error if field doesn't exist
$record.missing # ERROR
✅ Use ?
$record.missing? # null $record.missing? | default "N/A" # "N/A"
Empty Collections
Empty list/table checks
if ($list | is-empty) { ... }
Default value if empty
$list | default -e $val_if_empty
Advanced Topics
For advanced patterns and deeper dives, see:
references/advanced-patterns.md - Performance optimization, lazy evaluation, streaming, closures, memory-efficient patterns references/type-system.md - Complete type system guide, conversions, generics, type guards Best Practices Use type signatures - helps catch errors early Prefer pipelines - more idiomatic and composable Document with comments - # for inline, also # above declarations for doc comments Export selectively - don't pollute namespace Use default - handle null/missing gracefully Validate inputs - check types/ranges at function start Return consistent types - don't mix null and values unexpectedly Use modules - organize related functions Test incrementally - build complex pipelines step-by-step Prefix external commands with caret - ^grep instead of just grep. Makes it clear it's not a nushell command, avoids ambiguity. Nushell commands always have precedence, e.g. find is NOT usual Unix find tool: use ^find. Use dedicated external commands when needed - searching through lots of files is still faster with grep or rg, and large nested JSON structures will be processed much faster by jq Debugging Techniques
Print intermediate values
$data | each {|x| print $x; $x} # Prints and passes through
Inspect type
$value | describe
Debug point
debug # Drops into debugger (if available)
Timing
timeit { expensive-command }
External Resources Official Nushell Book Nushell Cookbook Type Signatures Guide Module System