•
10 min read

5 Reasons to โ™ก nushell

Iโ€™ve tried nushell before, but backed away after learning Iโ€™d have to rewrite my rcโ€™s and the scripts I use to streamline my terminal experience.

I was driven to come back because I recently came across starship. I played with it and after 15 minutes was able to recreate my oh-my-posh configuration with very little effort (except for transient prompts).

I was so impressed I immediately added starship to my stow collection and began using it and exploring the scripting language and configuration management.

Ultimately I will be switching to nushell as my login shell once I port all of my scripts because of these features.

Pipelines are a great way to pipe data

Everything in nushell has a datatype, and nushell has re-implemented some of the coreutils within itโ€™s environment. This means that not all commands will return plain text like in bash or zsh.

A very useful example is ls which outputs a table. This unlocks a lot of helpful features such as sorting and selecting from the output.

Heres an example where I try to list software projects I might be neglecting

$ ls dev/ | sort-by modified
โ•ญโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚  # โ”‚      name      โ”‚ type โ”‚  size   โ”‚   modified   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  0 โ”‚ dev/gtk        โ”‚ dir  โ”‚    12 B โ”‚ 2 years ago  โ”‚
โ”‚  1 โ”‚ dev/s0ix       โ”‚ dir  โ”‚ 1,012 B โ”‚ 2 years ago  โ”‚
โ”‚  2 โ”‚ dev/jupyter    โ”‚ dir  โ”‚    10 B โ”‚ 2 years ago  โ”‚
โ”‚  3 โ”‚ dev/framework  โ”‚ dir  โ”‚     8 B โ”‚ 2 years ago  โ”‚
โ”‚  4 โ”‚ dev/java       โ”‚ dir  โ”‚    50 B โ”‚ a year ago   โ”‚
โ”‚  5 โ”‚ dev/cloud      โ”‚ dir  โ”‚     0 B โ”‚ a year ago   โ”‚
โ”‚  6 โ”‚ dev/kalyke     โ”‚ dir  โ”‚    12 B โ”‚ a year ago   โ”‚
โ”‚  7 โ”‚ dev/wasm       โ”‚ dir  โ”‚     8 B โ”‚ a year ago   โ”‚
โ”‚  8 โ”‚ dev/rust       โ”‚ dir  โ”‚    96 B โ”‚ a year ago   โ”‚
โ”‚  9 โ”‚ dev/wikijs     โ”‚ dir  โ”‚     8 B โ”‚ a year ago   โ”‚
โ”‚ 10 โ”‚ dev/extern     โ”‚ dir  โ”‚    40 B โ”‚ a year ago   โ”‚
โ”‚ 11 โ”‚ dev/scratch    โ”‚ dir  โ”‚     0 B โ”‚ a year ago   โ”‚
โ”‚ 12 โ”‚ dev/blog       โ”‚ dir  โ”‚    50 B โ”‚ a year ago   โ”‚
โ”‚ 13 โ”‚ dev/gh         โ”‚ dir  โ”‚    28 B โ”‚ a year ago   โ”‚
โ”‚ 14 โ”‚ dev/play       โ”‚ dir  โ”‚   164 B โ”‚ a year ago   โ”‚
โ”‚ 15 โ”‚ dev/go         โ”‚ dir  โ”‚    26 B โ”‚ a year ago   โ”‚
โ”‚ 16 โ”‚ dev/bugs       โ”‚ dir  โ”‚    28 B โ”‚ 9 months ago โ”‚
โ”‚ 17 โ”‚ dev/python     โ”‚ dir  โ”‚    20 B โ”‚ 9 months ago โ”‚
โ”‚ 18 โ”‚ dev/callisto   โ”‚ dir  โ”‚   240 B โ”‚ 8 months ago โ”‚
โ”‚ 19 โ”‚ dev/interviews โ”‚ dir  โ”‚     8 B โ”‚ 5 months ago โ”‚
โ”‚ 20 โ”‚ dev/node       โ”‚ dir  โ”‚    48 B โ”‚ 2 months ago โ”‚
โ”‚ 21 โ”‚ dev/leetcode   โ”‚ dir  โ”‚    18 B โ”‚ 2 months ago โ”‚
โ”‚ 22 โ”‚ dev/resume     โ”‚ dir  โ”‚   210 B โ”‚ 2 months ago โ”‚
โ”‚ 23 โ”‚ dev/web        โ”‚ dir  โ”‚    28 B โ”‚ 4 days ago   โ”‚
โ”‚ 24 โ”‚ dev/interview  โ”‚ dir  โ”‚    96 B โ”‚ a minute ago โ”‚
โ”‚ 25 โ”‚ dev/iac        โ”‚ dir  โ”‚    42 B โ”‚ a minute ago โ”‚
โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  # โ”‚      name      โ”‚ type โ”‚  size   โ”‚   modified   โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

List of code projects in ~/dev sorted by modified date

The authors have great attention to detail and include the column names at the top and bottom of long tables.

Lets look at another common use case, such as getting the 5 largest binaries installed by my package manager.

$ ls /usr/bin/ | sort-by size | reverse | first 5
โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ # โ”‚       name        โ”‚ type โ”‚   size   โ”‚   modified   โ”‚
โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 0 โ”‚ /usr/bin/helm     โ”‚ file โ”‚ 79.0 MiB โ”‚ 3 months ago โ”‚
โ”‚ 1 โ”‚ /usr/bin/lapce    โ”‚ file โ”‚ 57.9 MiB โ”‚ a year ago   โ”‚
โ”‚ 2 โ”‚ /usr/bin/podman   โ”‚ file โ”‚ 44.5 MiB โ”‚ a month ago  โ”‚
โ”‚ 3 โ”‚ /usr/bin/lto-dump โ”‚ file โ”‚ 34.9 MiB โ”‚ 2 weeks ago  โ”‚
โ”‚ 4 โ”‚ /usr/bin/nu       โ”‚ file โ”‚ 34.6 MiB โ”‚ 3 months ago โ”‚
โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Top 5 largest files in /usr/bin

I am actually quite surprised helm is larger than lapce!

It is easy to support external binaries in nu

I currently maintain a cloud platform for a software organization. We deploy our workloads to Kubernetes. I use kubectl all day, which outputs a plaintext table by default, but isnโ€™t always easily legible.
Nushellโ€™s answer for many of these situations is detect columns which will pick up on the delimiter used and convert the output into a table. We can streamline this in our config.nu

alias k = kubectl

# Custom command
def kout [] {
  detect columns
}

This enables us to do things like detect which pods are restarting the most in our dev environment

k get po -n dev | kout | sort-by RESTARTS | first 5

It has Rustโ€™s match syntax

I love Rustโ€™s match statement. Itโ€™s a very intuitive way to handle flow control and helps express error handling much more concisely than if or case statements.

Back on the topic of Kubernetes, I often use two functions Iโ€™ve named chk and chn, which help me to streamline the process of switching between kube contexts, and cluster namespaces. Using nushell will not speed me up if I cannot use these with ease. So ultimately I converted them into nushell commands.

Starting with chn whose POSIX implementation looks like this:

function chn() {
  if [ -z $1 ]; then
    kubectl config view --minify -o 'jsonpath={..namespace}'
  else
    kubectl config set-context --current --namespace=$1
  fi
}

As a naive first attempt for chn I maintained the if else structure.

def chn [ns: string] {
  if not $ns {
    `kubectl config view --minify -o 'jsonpath={..namespace}'`
  } else {
    `kubectl config set-context --current --namespace=$ns`
  }
}

There are two problems with this approach. First, the backticks resolve to a literal $ns and not our first argument. Additionally if we ran chn without an argument, it will not print the current namespace, instead it will throw an error because chn requires at least on argument.

Nu shell scripting language feels a lot more like a simplified rust or typescript syntax. Notably it requires you to define an input argument as optional with ?.
After even further reading, I found the support for match. Ultimately I ended up with these as my new commands.

def chk [ctx?: string] {
  match $ctx {
    null => (kubectl config get-contexts)
    _ => (kubectl config use-context $ctx)
  }
}

def chn [ns?: string] {
  match $ns {
    null => (kubectl config view --minify -o 'jsonpath={..namespace}')
    _ => (kubectl config set-context --current --namespace $ns)
  }
}

With match, modules, and unit testing Nushell feels more like a programming language than a scripting language

It is cross-platform

Automating gitops through ci workflows involves a lot of headless scripting. Primarily we do this in bash and sometimes introduce python if the problem warrants more complex error handling.

Neither of these are easy to distribute. With bash if youโ€™re not careful, and you write them on a Mac, you may find that some of Macโ€™s coreutils do not behave the same as their GNU couterparts (looking at you grep and date ๐Ÿซ )

The problem with python is that once you pull in a dependency you unknowingly get pinned to a specific glibc version (try running az cli in an older ubuntu container) and you have to install, or cache, those dependencies each workflow run. I hope your requirements.txt / pyproject.toml / uv.yaml are pinning the correct versions.

I was very happy to see that I can copy my config.nu from my Framework 13 (Fedora) to my work Macbook and my aliases and scripts all work without issue.

I will still run into date -d and need to install grep from homebrew if I want perl regex. However, writing nu scripts tells a better story long term, especially knowing itโ€™s type safe and has significantly easier error handling than POSIX scripts.

These scripts also work out of the box on Windows. Nu offers cross platform compatibility for / even when executed outside of wsl.

It has a built in http client

Back on the topic of ci workflows. Something commonly used is piping curl and wget into yq and jq to consume serialized data from apis. We cannot assume yq/jq are available and write reusable actions to install them that become boilerplate in several workflows.

You may think โ€œwhy not simply use curl? ๐ŸคŒโ€. Well, what if this:

curl https://jsonplaceholder.typicode.com/comments | jq '.[-5:-1][].name' > 5-most-recent-comments.json

Could become this:

http get https://jsonplaceholder.typicode.com/comments | last 5 | get name | to json | save 5-most-recent-comments.json

Yes itโ€™s a tad longer, however itโ€™s more intuitive to a human, reads more clearly, and is significantly less error prone than jqโ€™s dsl.

Furthermore, when using workflow matrices, or approval gates, on workflow environments you cannot use act. As a result your feedback loop extends significantly to the process of pushing to a branch and waiting for the pipeline output for show any errors.

This means time spent on errors adds up significantly when youโ€™re building workflows in bash. And Python could become a ticking timebomb waiting to aggravate you later when workflow runners or the python version are updated.

Replacing these with unit tested nu scripts has me more confident that I wonโ€™t accidentally push a regression and cause headaches later.

Its a nu day, and a nu life, and Iโ€™m feeling good

And so here I am, porting my POSIX scripts to nu in my free time. It will cost me a bit of time, but the time savings when it comes to maintenance will compound over time.

And lets me real, no body maintains things for fun, weโ€™re here to build.

๐Ÿป