Nushell
Nushell is a functional, structured-data language that happens to be a shell. Write it that way: pipelines of transformations over records and tables, not bash with different syntax.
Why Nushell
Functional over structured data: JSON, ls, and command output stay records through the whole pipeline instead of being re-parsed as text. So no jq/sed/awk/date to install, wrap, or write twice for macOS and Linux — and Nix pins the interpreter.
Which language to pick for a given script is in the repo's AGENTS.md.
Steps
- Read the docs for the topic at hand (see below) before writing any Nushell.
- Write the code functionally — see Rules.
- Verify every construct against the installed version before claiming it works. Nushell breaks and renames things between minor releases, so do not trust memory of a command's signature:
sh
nu --version nu -c '<snippet>' # run a snippet nu --ide-check 10 <file> # parse/typecheck a script nu -c 'help <command>' # check a signature - Format before committing:
sh
nufmt <file> # rewrites in place nufmt --dry-run <file> # non-zero exit if it would change
Docs
Read before writing:
- https://www.nushell.sh/book/thinking_in_nu.html
- https://www.nushell.sh/book/nushell_map_functional.html
- https://www.nushell.sh/book/nushell_map_imperative.html
- https://www.nushell.sh/book/coming_to_nu.html
- https://www.nushell.sh/book/style_guide.html
Every page of the book is in the sidebar of those, as are /commands/, /cookbook/, and /lang-guide/. Follow the sidebar to whatever the task needs instead of guessing.
Rules
Functional style is the default. In review, treat each of these as a defect to fix:
mut+foras an accumulator. Replace withreduce,each,where,group-by,zip,flatten,insert/update,generate.mutis legitimate only for genuinely sequential state that no filter expresses.- String plumbing between steps. Pass records and tables. Serialise once at the boundary (
to json --raw), parse once on the way in (from json). - Nested
if/elsechains on a value's shape. Usematch, including list patterns ([],[$first, ..$rest]) and guards. - Reimplementing a builtin. Check
https://www.nushell.sh/commands/categories/filters.htmlfirst. - Shelling out for data. Prefer native commands over
^jq,^sed,^awk,^date. When an external command is genuinely needed, pipe its output throughfrom jsonetc. immediately. - Bare
eachfor effects. If the result is unused, say so with| ignore; if it produces values, keep them in the pipeline.
Also:
- Give every script a
def main. - Type custom command signatures — parameter types,
--flag, and the return type. They document intent and are checked at parse time.nufmtstrips a redundant: any; leave it stripped. - Timestamps are
datetime, durations areduration. Compare and subtract them directly instead of formatting to strings. Convert explicitly to UTC (| date to-timezone UTC) when the value crosses a boundary that expects it. - Fail loudly with
error makerather than returning a sentinel value.
In this repo
- CI workflow steps run Nushell via
shell: nu {0}withhustcer/setup-nu. See.github/workflows/_update-flake-reusable.yamlfor the established style. - Pin the interpreter. Workflows pass an explicit
version:tosetup-nu; keep doing that, or a Nushell release will break a script that never changed. - Standalone scripts live alongside their consumer, e.g.
nix/packages/git-wtpr/git-wtpr.nu. - Neither
nunornufmtis on the interactive PATH; use themissing-toolsskill to reach them.

