Nushell Coding Skill
A practical guide for writing effective nushell code.
Looking Up Information
Built-in Help System
- •
help <command>- Get detailed help for any command - •
help commands- List all available commands - •
help --find <term>- Search help for a specific term - •
<command> --help- Quick help flag for most commands
Type Information
- •
<value> | describe- Get the type of any value - •
<value> | describe --detailed- Get detailed type information including structure
Documentation Resources
- •Official docs: https://www.nushell.sh/
- •Book: https://www.nushell.sh/book/
- •Cookbook: https://www.nushell.sh/cookbook/
Invoking Nushell Commands
From Other Shells (bash/zsh)
# Execute a single nushell command nu -c "use module.nu; module command" # Run a nushell script nu script.nu # Start an interactive nushell session nu
Module System
# Import and use a module use module.nu module command-name # Import specific exports use module.nu [command1, command2] # Import with alias use module.nu *
Pipeline Execution
Nushell is built around pipelines where data flows from left to right:
# Data flows through pipeline
$data | filter {|item| $item.value > 10} | each {|item| $item.name}
# Commands can be chained
ls | where size > 1mb | sort-by modified | reverse
Functional Coding Style
Immutability
Prefer immutable variables over mutable ones:
# Preferred: immutable
let result = ($data | where {|item| $item.active == true})
# Avoid: mutable unless necessary
mut result = []
for item in $data {
if $item.active {
$result = ($result | append $item)
}
}
# Exception: mutable variables are appropriate when state must persist across loop iterations
mut current_items = $initial_items
loop {
# Rebuild choices based on current state each iteration
let choices = ($current_items | each {|item| $item.description})
let selection = ($choices | input list "Select: ")
if $selection == "Add" {
$current_items = (add_item $current_items) # Updates persist to next iteration
}
}
Pure Functions
Write functions that return data rather than mutating state:
# Good: returns new data
def add_entry [entries, new_entry] {
$entries | append $new_entry
}
# Avoid: mutates global state
def add_entry [new_entry] {
$env.entries = ($env.entries | append $new_entry)
}
Function Composition
Break down complex operations into smaller, composable functions:
# Good: small, focused functions
def filter_active [items] {
$items | where active == true
}
def extract_names [items] {
$items | each {|item| $item.name}
}
def get_active_names [items] {
$items | filter_active | extract_names
}
# Avoid: large functions that do multiple things
def get_active_names [items] {
$items | where active == true | each {|item| $item.name} | sort | uniq | # ... more logic
}
Prefer Pipelines Over Intermediate Variables
# Good: direct pipeline
let result = ($data
| where status == "active"
| sort-by priority
| first 10)
# Less ideal: unnecessary intermediate variables
let filtered = ($data | where status == "active")
let sorted = ($filtered | sort-by priority)
let result = ($sorted | first 10)
Common Pitfalls
Constants as Default Parameters
Problem: Using a constant name as a default parameter value converts it to a string, not the constant's value.
const DEFAULT_CONFIG = {timeout: 30, retries: 3}
# Wrong: $config will be the string "DEFAULT_CONFIG"
def process [config = DEFAULT_CONFIG] {
# $config is a string, not a record!
}
# Correct: use optional parameter with explicit check
def process [config?: record] {
let config = if ($config | is-empty) { $DEFAULT_CONFIG } else { $config }
# Now $config is properly a record
}
Pipeline vs Variable Context
Problem: Some commands expect pipeline input and don't work with variable arguments.
let items = [{name: "foo"}, {name: "bar"}]
# This may fail depending on nushell version
let result = ($items | where name == "foo")
# More reliable: use closures with where
let result = ($items | where {|e| $e.name == "foo"})
# Or use each with filtering
let result = ($items | each {|e| if $e.name == "foo" { $e } else { null }} | compact)
Type Expectations in Pipelines
Problem: Commands in pipelines have specific input type requirements.
# Check what type a value is $value | describe # Ensure correct type is being passed # Example: some commands expect tables, others expect lists or strings let data = (open file.json) # Returns structured data $data | where field == value # Expects table/list input
Variable Shadowing in Loops
Problem: Using let with the same name as an outer variable creates a new local variable that shadows the outer one. Changes don't persist to the next iteration.
def display_menu [menu_entries?: list] {
let menu_entries = if ($menu_entries | is-empty) { $default } else { $menu_entries }
loop {
let selection = ($menu_entries | input list)
# Wrong: this creates a NEW local variable, doesn't update the outer one
let menu_entries = (add_entry $menu_entries)
# After this block, the outer $menu_entries is unchanged in next iteration!
}
}
# Correct: use mut for variables that need to persist across iterations
def display_menu [menu_entries?: list] {
mut current_entries = if ($menu_entries | is-empty) { $default } else { $menu_entries }
loop {
let selection = ($current_entries | input list)
# This properly updates the mutable variable for the next iteration
$current_entries = (add_entry $current_entries)
}
}
Empty Checks
Problem: Different types have different empty states.
# For lists/tables
if ($list | is-empty) { }
# For strings
if ($str | str length) == 0 { }
# For null/nothing
if ($value == null) { }
# For optional parameters
if ($param | is-empty) { }
Best Practices Summary
- •Look up types - Use
describeliberally when debugging - •Read the help -
help <command>is comprehensive and accurate - •Test incrementally - Build pipelines step by step, checking output at each stage
- •Prefer immutability - Use
letovermutwhenever possible - •Return data - Functions should return values, not mutate globals
- •Compose functions - Break complex logic into small, reusable functions
- •Use closures - Modern nushell prefers closures with
where,each, etc. - •Check types - When errors occur, verify input/output types match expectations