Using_nushell_with_nixos
Nushell and NixOS

I recently switched default shells from zsh to nushell, this post is about some of the challenges and advantages of using nushell with NixOS.
Nushell borrows concepts from many shells and languages and is itself both a programming language and a shell. Because of this, it has it’s own way of working with files, directories, websites, and more.
Nushell is powerful and has many essential commands built directly into the shell (“internal” commands) rather than a link to an executable. You can use this set of commands across different operating systems, having this consistency is helpful when creating cross-platform code.
ls # Internal command
^ls # External command (typically /usr/bin/ls)
The Reedline-Editor is powerful and provides good
vi-mode
oremacs
support built in.It’s default
Ctrl+r
history command is nice and structured.Nushell has helpful rust like error messages
Fundamentally designed as a structured data scripting language: and then it acts as a shell on top of that foundation. This “language first” approach is what gives it many of its distinctive features and makes it a powerful scripting language.A few of those features are:
Pipelines of structured data: Unlike traditional shells that primarily deal with plain text streams, Nushell pipelines operate on tables of structured data. Each command can understand and manipulate this structured data directly.
Consistent syntax: Its syntax is more consistent and predictable compared to the often quirky syntax of Bash and Zsh, drawing inspiration from other programming languages.
Strong typing Nushell has a type system, which helps catch errors early and allows for more robust scripting.
First-class data types: It treats various data formats (like JSON, CSV, TOML) as native data types, making it easier to work with them. Because of this, Nushell aims to replace the need for external tools like
jq
,awk
,sed
,cut
, and even some uses ofgrep
andcurl
.
Variables are Immutable by Default: Nushell’s commands are based on a functional-style of programming which requires immutability, sound familiar?
Nushell’s Environment is Scoped: Nushell takes many design cues from compiled languages, one is that languages should avoid global mutable state. Shells have commonly used global mutation to update the environment, Nushell attempts to steer clear of this increasing reproducability.
Single-use Environment Variables:
FOO=BAR $env.FOO
# => BAR
- Permanent Environment Variables: In your
config.nu
# config.nu
$env.FOO = 'BAR'
The Challenges
There are many similarities so it can be easy to forget that some Bash (and POSIX in general) style constructs just won’t work in Nushell. Considering that NixOS seems to have been designed for bash, even Zsh isn’t fully compatable you may want to think twice before you choose Nushell as your default.
The documentation is incomplete, it’s not as mature as other shells including fish.
&&
doesn’t work use;
instead.>
is used as the greater-than operator for comparisons:
"hello" | save output.txt
is equivalent to the following in bash:
echo "hello" > output.txt
- If you notice above the nushell command doesn’t require an
echo
prefix, this is because Nushell has Implicit Return:
"Hello, World" == (echo "Hello, World")
# => true
The above example shows that the string,
"Hello, World"
is equivalent to the output value fromecho "Hello, World"
Every Command Returns a Value:
let p = 7
print $p # 7
$p * 6 # 42
Multi-Line Editing:
When writing a long command you can press Enter to add a newline and move to the next line. For example:
ls | # press enter
where name =~ | # press enter, comments after pipe ok
get name | # press enter
mv ...$in ./backups/
This allows you to cycle through the entire multi-line command using the up and down arrow keys and then customize different lines or sections of the command.
You can manually insert a newline using
Alt+Enter
orShift+Enter
.
Key Differences Between Nushell & Bash
Feature | Bash (Dynamic) | Nushell (Static) |
---|---|---|
Code Execution | Line-by-line | Whole script parsed first |
Error Detection | Runtime errors only | Catches errors before running |
Support for eval | ✅ Allowed | ❌ Not supported |
Custom Parsing | Limited | Built-in semantic analysis |
IDE Features | Basic syntax highlighting | Advanced integration, linting, and formatting |
Nushell Showcase
Ctrl+t
List Commands with carapace and fzf:Carapace
Carapace-Bin Install:Carapace
man example:
Custom Nushell Commands
- The following command allows you to choose which input to update interactively with fzf.
# nix.nu
# upgrade system packages
# `nix-upgrade` or `nix-upgrade -i`
def nix-upgrade [
flake_path: string = "/home/jr/flake", # path that contains a flake.nix
--interactive (-i) # select packages to upgrade interactively
]: nothing -> nothing {
let working_path = $flake_path | path expand
if not ($working_path | path exists) {
echo "path does not exist: $working_path"
exit 1
}
let pwd = $env.PWD
cd $working_path
if $interactive {
let selections = nix flake metadata . --json
| from json
| get locks.nodes
| columns
| str join "\n"
| fzf --multi --tmux center,20%
| lines
# Debug: Print selections to verify
print $"Selections: ($selections)"
# Check if selections is empty
if ($selections | is-empty) {
print "No selections made."
cd $pwd
return
}
# Use spread operator to pass list items as separate arguments
nix flake update ...$selections
} else {
nix flake update
}
cd $pwd
nh os switch $working_path
}

- The
ns
command is designed to search for Nix packages usingnix search
and present the results in a cleaner format, specifically removing the architecture and operating system prefix that nix search often includes.
def ns [
term: string # Search target.
] {
let info = (
sysctl -n kernel.arch kernel.ostype
| lines
| {arch: ($in.0|str downcase), ostype: ($in.1|str downcase)}
)
nix search --json nixpkgs $term
| from json
| transpose package description
| flatten
| select package description version
| update package {|row| $row.package | str replace $"legacyPackages.($info.arch)-($info.ostype)." ""}
}

nufetch
command:
# `nufetch` `(nufetch).packages`
def nufetch [] {
{
"kernel": $nu.os-info.kernel_version,
"nu": $env.NU_VERSION,
"packages": (ls /etc/profiles/per-user | select name | prepend [[name];["/run/current-system/sw"]] | each { insert "number" (nix path-info --recursive ($in | get name) | lines | length) | insert "size" ( nix path-info -S ($in | get name) | parse -r '\s(.*)' | get capture0.0 | into filesize) | update "name" ($in | get name | parse -r '.*/(.*)' | get capture0.0 | if $in == "sw" {"system"} else {$in}) | rename "environment"}),
"uptime": (sys host).uptime
}
}

duf
command, I have mine aliased todf
:

ps
command:

- Adding the following to your
configuration.nix
will show you the diff of the closures on rebuild:
# configuration.nix
# During system activation, compare the closure size difference between the
# current and new system and display a formatted table if significant changes are
# detected.
system.activationScripts.diff = ''
if [[ -e /run/current-system ]]; then
${pkgs.nushell}/bin/nu -c "let diff_closure = (${pkgs.nix}/bin/nix store
diff-closures /run/current-system '$systemConfig'); let table =
(\$diff_closure | lines | where \$it =~ KiB | where \$it =~ → | parse -r
'^(?<Package>\S+): (?<Old>[^,]+)(?:.*) → (?<New>[^,]+)(?:.*), (?<DiffBin>.*)$'
| insert Diff { get DiffBin | ansi strip | into filesize } | sort-by -r Diff
| reject DiffBin); if (\$table | get Diff | is-not-empty) { print \"\"; \$table
| append [[Package Old New Diff]; [\"\" \"\" \"\" \"\"]] | append [[Package Old
New Diff]; [\"\" \"\" \"Total:\" (\$table | get Diff | math sum) ]]
| print; print \"\" }"
fi
'';

nix-list-system
command lists all installed packages:
# list all installed packages
def nix-list-system []: nothing -> list<string> {
^nix-store -q --references /run/current-system/sw
| lines
| filter { not ($in | str ends-with 'man') }
| each { $in | str replace -r '^[^-]*-' '' }
| sort
}

Using Just and Justfiles
The following is my
justfile
that I keep right next to myflake.nix
it simplifies some commands and makes things work that weren’t working with nushell for my case, you’ll have to change it to match your configuration. It’s not perfect but works for my use case, take whats useful and leave the rest.You’ll first need to install
just
to make use ofjustfiles
.
# nix shell nixpkgs#just nixpkgs#nushell
set shell := ["nu", "-c"]
flake_path := "/home/jr/flake"
hostname := "magic"
home_manager_output := "jr@magic"
utils_nu := absolute_path("utils.nu")
default:
@just --list
# Rebuild
[group('nix')]
fr:
nh os switch --hostname {{hostname}} {{flake_path}}
# Flake Update
[group('nix')]
fu:
nh os switch --hostname {{hostname}} --update {{flake_path}}
# Update specific input
# Usage: just upp nixpkgs
[group('nix')]
upp input:
nix flake update {{input}}
# Test
[group('nix')]
ft:
nh os test --hostname {{hostname}} {{flake_path}}
# Collect Garbage
[group('nix')]
ncg:
nix-collect-garbage --delete-old ; sudo nix-collect-garbage -d ; sudo /run/current-system/bin/switch-to-configuration boot
[group('nix')]
cleanup:
nh clean all
# Clean
[group('nix')]
clean:
sudo nix profile wipe-history --profile /nix/var/nix/profiles/system --older-than 3d
# Upgrade
[group('nix')]
upd:
nh os switch -u {{flake_path}} ; nh os switch --hostname {{hostname}} {{flake_path}}
[group('nix')]
eval:
nix-instantiate --eval --json --strict | jq
# Nix Repl flake:nixpkgs
[group('nix')]
repl:
nix repl -f flake:nixpkgs
# format the nix files in this repo
[group('nix')]
fmt:
nix fmt
# Show all the auto gc roots in the nix store
[group('nix')]
gcroot:
ls -al /nix/var/nix/gcroots/auto/
# Verify all store entries
[group('nix')]
verify-store:
nix store verify --all
[group('nix')]
repair-store *paths:
nix store repair {{paths}}
# Usage: `./result/bin/run-*-vm`
# may need to set initialHashedPassword first
[group('nix')]
vm:
sudo nixos-rebuild build-vm
system-info:
"This is an {{arch()}} machine"
running:
ps | where status == Running
help:
help commands | explore
# =================================================
#
# Other useful commands
#
# =================================================
[group('common')]
path:
$env.PATH | split row ":"
[group('common')]
trace-access app *args:
strace -f -t -e trace=file {{app}} {{args}} | complete | $in.stderr | lines | find -v -r "(/nix/store|/newroot|/proc)" | parse --regex '"(/.+)"' | sort | uniq
[linux]
[group('common')]
penvof pid:
sudo cat $"/proc/($pid)/environ" | tr '\0' '\n'
# Remove all reflog entries and prune unreachable objects
[group('git')]
ggc:
git reflog expire --expire-unreachable=now --all
git gc --prune=now
# Amend the last commit without changing the commit message
[group('git')]
game:
git commit --amend -a --no-edit
[group('git')]
push:
git push -u origin main
# Delete all failed pods
[group('k8s')]
del-failed:
kubectl delete pod --all-namespaces --field-selector="status.phase==Failed"
[linux]
[group('services')]
list-inactive:
systemctl list-units -all --state=inactive
[linux]
[group('services')]
list-failed:
systemctl list-units -all --state=failed
[linux]
[group('services')]
list-systemd:
systemctl list-units systemd-*
# List journal
[linux]
[group('services')]
jctl:
^jctl = "journalctl -p 5 -xb";
- To list available commands type, (you must be in the same directory as the
justfile):
just

So
just list-failed
will list any failed systemd services for example.A lot of the
.nu
files came from this repo by BlindFS:modern-dot-files he uses Nix Darwin so there are many changes for NixOS.
my-nu-config Warning, it’s very complex and hard to understand. Just know that from my
shells
directory I import thenushell
directory which contains adefault.nix
which is the entrypoint for this configuration. Thedefault.nix
hasconfigFile.source = ./config.nu;
which integrates all the.nu
files. I know it’s a mess, I’ll refactor shortly.The examples use my-starship-config the logic at the end works for
bash
,zsh
, andnushell
.If you wan’t to use my config you’ll have to enable the experimental-feature
pipe-operators
in the same place you enable flakes and nix-command.
There are still situations where I need to switch to zsh or bash to get something to work i.e.
nix-shell
and a few others.
Resources
nu_scripts some of the custom commands came from here.
discord You can find custom commands, configurations, etc here.