Elevate your CLI experience: Nushell (upd.)
Posted Jan 02, 2024. Updated Jan 05, 2024 ‐ 4 min read
25 years of using bash
Yes, I did it to myself. I used bash shell for the past 25 years. I started with bash 1.14 on Red Hat 6.0 Hedwig, and I didn't had much need of change over the years. Of course I checked other shells. At the beginning, from my point of view, bash was the most user friendly.
Years passed by, and my CLI work environment didn't changed much. For some reason I liked these old boring tools. But spending years in monotonous environment could be one of the reason why I'm battling with burnout now.
That is why I decided to change my work environment to something more modern and feature reach. In this series of articles I'll present modern alternatives of old tools.
Nushell
Nushell is "a new type of shell" according to a description on its website. But what does it mean?
First thing that is important, it works on BSD, Linux, macOS and Windows. I'm ok with PowerShell, but it's cool to have an option to replace it with Nu.
Second thing is pipelining. You can do pipelining in all shells(?). But in Nu it's a little different. Build in commands uses structured data. It means you can search, sort and pipeline it to other commands. We will see what does it mean in following examples.
Installation
Let's install Nushell.
cargo install --force nu
There are many ways to install it. I prefer installing the latest version with cargo.
Nushell uses some colours by default. It's a nice change.
Usage
But let's see how it works.
~> ps | take 5 01/01/24 17:04:36 PM
â•â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â•®
│ # │ pid │ ppid │ name │ status │ cpu │ mem │ virtual │
├───┼─────┼──────┼──────────────┼──────────┼──────┼──────────┼──────────┤
│ 0 │ 1 │ 0 │ /sbin/init │ Sleeping │ 0.00 │ 11.9 MiB │ 99.9 MiB │
│ 1 │ 2 │ 0 │ kthreadd │ Sleeping │ 0.00 │ 0 B │ 0 B │
│ 2 │ 3 │ 2 │ rcu_gp │ Unknown │ 0.00 │ 0 B │ 0 B │
│ 3 │ 4 │ 2 │ rcu_par_gp │ Unknown │ 0.00 │ 0 B │ 0 B │
│ 4 │ 5 │ 2 │ slub_flushwq │ Unknown │ 0.00 │ 0 B │ 0 B │
╰───┴─────┴──────┴──────────────┴──────────┴──────┴──────────┴──────────╯
In this example I limited to only five results. As you can see, everything is in a tabular form. You can use column names for sorting and filtering purposes.
Let's try to do something more practical.
~> ps | where mem > 1MiB | where virtual > 10MiB 01/01/24 17:06:31 PM
â•â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â•®
│ # │ pid │ ppid │ name │ status │ cpu │ mem │ virtual │
├────┼──────┼──────┼───────────────────────────────┼──────────┼───────┼──────────┼───────────┤
│ 0 │ 1 │ 0 │ /sbin/init │ Sleeping │ 0.00 │ 11.9 MiB │ 99.9 MiB │
│ 1 │ 240 │ 1 │ /lib/systemd/systemd-journald │ Sleeping │ 0.00 │ 23.8 MiB │ 56.3 MiB │
│ 2 │ 269 │ 1 │ /lib/systemd/systemd-udevd │ Sleeping │ 0.00 │ 5.9 MiB │ 25.3 MiB │
│ 3 │ 416 │ 1 │ /lib/systemd/systemd-logind │ Sleeping │ 0.00 │ 7.7 MiB │ 24.3 MiB │
│ 4 │ 432 │ 1 │ /sbin/wpa_supplicant │ Sleeping │ 0.00 │ 5.8 MiB │ 16.1 MiB │
│ 5 │ 479 │ 1 │ /usr/bin/containerd │ Sleeping │ 0.00 │ 49.8 MiB │ 1.4 GiB │
│ 6 │ 493 │ 1 │ sshd: │ Sleeping │ 0.00 │ 9.1 MiB │ 15.0 MiB │
│ 7 │ 535 │ 1 │ /usr/sbin/nmbd │ Sleeping │ 0.00 │ 15.5 MiB │ 68.9 MiB │
│ 8 │ 545 │ 1 │ /usr/sbin/dockerd │ Sleeping │ 0.00 │ 80.6 MiB │ 1.4 GiB │
│ 9 │ 557 │ 1 │ /usr/sbin/smbd │ Sleeping │ 0.00 │ 21.4 MiB │ 81.3 MiB │
│ 10 │ 562 │ 557 │ /usr/sbin/smbd │ Sleeping │ 0.00 │ 8.9 MiB │ 79.6 MiB │
│ 11 │ 563 │ 557 │ /usr/sbin/smbd │ Sleeping │ 0.00 │ 4.5 MiB │ 79.6 MiB │
│ 12 │ 738 │ 493 │ sshd: │ Sleeping │ 0.00 │ 10.4 MiB │ 17.0 MiB │
│ 13 │ 743 │ 1 │ /lib/systemd/systemd │ Sleeping │ 0.00 │ 10.4 MiB │ 18.5 MiB │
│ 14 │ 744 │ 743 │ (sd-pam) │ Sleeping │ 0.00 │ 3.1 MiB │ 100.4 MiB │
│ 15 │ 763 │ 738 │ sshd: │ Sleeping │ 0.00 │ 6.3 MiB │ 17.2 MiB │
│ 16 │ 767 │ 493 │ sshd: │ Sleeping │ 0.00 │ 10.4 MiB │ 17.0 MiB │
│ 17 │ 773 │ 767 │ sshd: │ Sleeping │ 0.00 │ 6.3 MiB │ 17.2 MiB │
│ 18 │ 780 │ 764 │ nu │ Sleeping │ 0.00 │ 44.6 MiB │ 1.2 GiB │
│ 19 │ 836 │ 557 │ /usr/sbin/smbd │ Sleeping │ 0.00 │ 16.7 MiB │ 89.9 MiB │
│ 20 │ 848 │ 780 │ zola │ Sleeping │ 0.00 │ 27.3 MiB │ 1.0 GiB │
│ 21 │ 1005 │ 774 │ nu │ Sleeping │ 0.00 │ 45.0 MiB │ 1.2 GiB │
│ 22 │ 2462 │ 493 │ sshd: │ Sleeping │ 0.00 │ 10.4 MiB │ 17.0 MiB │
│ 23 │ 2468 │ 2462 │ sshd: │ Sleeping │ 0.00 │ 6.3 MiB │ 17.2 MiB │
│ 24 │ 2472 │ 2469 │ nu │ Running │ 19.48 │ 47.6 MiB │ 1.2 GiB │
├────┼──────┼──────┼───────────────────────────────┼──────────┼───────┼──────────┼───────────┤
│ # │ pid │ ppid │ name │ status │ cpu │ mem │ virtual │
╰────┴──────┴──────┴───────────────────────────────┴──────────┴───────┴──────────┴───────────╯
Let's filter it a little more and use some sorting.
~> ps | where mem > 1MiB | where virtual > 10MiB | where status == Sleeping | sort-by ppid 01/01/24 17:10:04 PM
â•â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â•®
│ # │ pid │ ppid │ name │ status │ cpu │ mem │ virtual │
├────┼──────┼──────┼───────────────────────────────┼──────────┼──────┼──────────┼───────────┤
│ 0 │ 1 │ 0 │ /sbin/init │ Sleeping │ 0.00 │ 11.9 MiB │ 99.9 MiB │
│ 1 │ 240 │ 1 │ /lib/systemd/systemd-journald │ Sleeping │ 0.00 │ 23.8 MiB │ 56.3 MiB │
│ 2 │ 269 │ 1 │ /lib/systemd/systemd-udevd │ Sleeping │ 0.00 │ 5.9 MiB │ 25.3 MiB │
│ 3 │ 416 │ 1 │ /lib/systemd/systemd-logind │ Sleeping │ 0.00 │ 7.7 MiB │ 24.3 MiB │
│ 4 │ 432 │ 1 │ /sbin/wpa_supplicant │ Sleeping │ 0.00 │ 5.8 MiB │ 16.1 MiB │
│ 5 │ 479 │ 1 │ /usr/bin/containerd │ Sleeping │ 0.00 │ 49.8 MiB │ 1.4 GiB │
│ 6 │ 493 │ 1 │ sshd: │ Sleeping │ 0.00 │ 9.1 MiB │ 15.0 MiB │
│ 7 │ 535 │ 1 │ /usr/sbin/nmbd │ Sleeping │ 0.00 │ 15.5 MiB │ 68.9 MiB │
│ 8 │ 545 │ 1 │ /usr/sbin/dockerd │ Sleeping │ 0.00 │ 80.6 MiB │ 1.4 GiB │
│ 9 │ 557 │ 1 │ /usr/sbin/smbd │ Sleeping │ 0.00 │ 21.4 MiB │ 81.3 MiB │
│ 10 │ 743 │ 1 │ /lib/systemd/systemd │ Sleeping │ 0.00 │ 10.4 MiB │ 18.5 MiB │
│ 11 │ 738 │ 493 │ sshd: │ Sleeping │ 0.00 │ 10.4 MiB │ 17.0 MiB │
│ 12 │ 767 │ 493 │ sshd: │ Sleeping │ 0.00 │ 10.4 MiB │ 17.0 MiB │
│ 13 │ 2462 │ 493 │ sshd: │ Sleeping │ 0.00 │ 10.4 MiB │ 17.0 MiB │
│ 14 │ 562 │ 557 │ /usr/sbin/smbd │ Sleeping │ 0.00 │ 8.9 MiB │ 79.6 MiB │
│ 15 │ 563 │ 557 │ /usr/sbin/smbd │ Sleeping │ 0.00 │ 4.5 MiB │ 79.6 MiB │
│ 16 │ 836 │ 557 │ /usr/sbin/smbd │ Sleeping │ 0.00 │ 16.7 MiB │ 89.9 MiB │
│ 17 │ 763 │ 738 │ sshd: │ Sleeping │ 0.00 │ 6.3 MiB │ 17.2 MiB │
│ 18 │ 744 │ 743 │ (sd-pam) │ Sleeping │ 0.00 │ 3.1 MiB │ 100.4 MiB │
│ 19 │ 780 │ 764 │ nu │ Sleeping │ 0.00 │ 44.6 MiB │ 1.2 GiB │
│ 20 │ 773 │ 767 │ sshd: │ Sleeping │ 0.00 │ 6.3 MiB │ 17.2 MiB │
│ 21 │ 1005 │ 774 │ nu │ Sleeping │ 0.00 │ 45.0 MiB │ 1.2 GiB │
│ 22 │ 848 │ 780 │ zola │ Sleeping │ 0.00 │ 27.6 MiB │ 1.0 GiB │
│ 23 │ 2468 │ 2462 │ sshd: │ Sleeping │ 0.00 │ 6.3 MiB │ 17.2 MiB │
╰────┴──────┴──────┴───────────────────────────────┴──────────┴──────┴──────────┴───────────╯
Nice. Let's try to find everything related to systemd.
~> ps | where mem > 1MiB | where virtual > 10MiB | where status == Sleeping | sort-by ppid | find name = "systemd" 01/01/24 17:13:00 PM
â•â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â•®
│ # │ pid │ ppid │ name │ status │ cpu │ mem │ virtual │
├───┼─────┼──────┼───────────────────────────────┼──────────┼──────┼──────────┼──────────┤
│ 0 │ 240 │ 1 │ /lib/systemd/systemd-journald │ Sleeping │ 0.00 │ 23.8 MiB │ 56.3 MiB │
│ 1 │ 269 │ 1 │ /lib/systemd/systemd-udevd │ Sleeping │ 0.00 │ 5.9 MiB │ 25.3 MiB │
│ 2 │ 416 │ 1 │ /lib/systemd/systemd-logind │ Sleeping │ 0.00 │ 7.7 MiB │ 24.3 MiB │
│ 3 │ 743 │ 1 │ /lib/systemd/systemd │ Sleeping │ 0.00 │ 10.4 MiB │ 18.5 MiB │
╰───┴─────┴──────┴───────────────────────────────┴──────────┴──────┴──────────┴──────────╯
Tables are cool but not always practical. What would you say about outputing it to JSON?
~> ps | where mem > 1MiB | where virtual > 10MiB | where status == Sleeping | sort-by ppid | find name = "systemd" | to json 01/01/24 17:14:52 PM
[
{
"pid": 240,
"ppid": 1,
"name": "/lib/systemd/systemd-journald",
"status": "Sleeping",
"cpu": 0,
"mem": 24911872,
"virtual": 59047936
},
{
"pid": 269,
"ppid": 1,
"name": "/lib/systemd/systemd-udevd",
"status": "Sleeping",
"cpu": 0,
"mem": 6184960,
"virtual": 26550272
},
{
"pid": 416,
"ppid": 1,
"name": "/lib/systemd/systemd-logind",
"status": "Sleeping",
"cpu": 0,
"mem": 8105984,
"virtual": 25481216
},
{
"pid": 743,
"ppid": 1,
"name": "/lib/systemd/systemd",
"status": "Sleeping",
"cpu": 0,
"mem": 10887168,
"virtual": 19410944
}
]
Maybe CSV format has more sense?
~> ps | where mem > 1MiB | where virtual > 10MiB | where status == Sleeping | sort-by ppid | find name = "systemd" | to csv 01/01/24 17:15:54 PM
pid,ppid,name,status,cpu,mem,virtual
240,1,/lib/systemd/systemd-journald,Sleeping,0,23.8 MiB,56.3 MiB
269,1,/lib/systemd/systemd-udevd,Sleeping,0,5.9 MiB,25.3 MiB
416,1,/lib/systemd/systemd-logind,Sleeping,0,7.7 MiB,24.3 MiB
743,1,/lib/systemd/systemd,Sleeping,0,10.4 MiB,18.5 MiB
I hope that you understand now what makes Nushell so cool.
Configuration (2024-01-05)
To configure Nushell, you need to execute the following command.
config nu
It will open your prefered text editor. If you don't have configured prefered text editor, you may want to read "Elevate your CLI experience: Helix". Or use your favourite editor to edit ~/.config/nushell/config.nu file.
I decided to update default dark theme to use my preferred colours. I have an advanced glaucoma, and I need to have a well visible colours on my screen.
Here are colours that I'm using:
- #040404 - Onyx
- #c02310 - Ruby
- #257d10 - Emerald
- #00327b - Sapphire
- #3b96bf - Aquamarine
- #703b6c - Amethyst
- #bc7800 - Topaz
- #d0d9d7 - Diamond
- #100f0f - Onyx light
- #dc4025 - Ruby light
- #45a52d - Emerald light
- #0e4a8a - Sapphire light
- #83c0d2 - Aquamarine light
- #83647f - Amethyst light
- #cb910e - Topaz light
- #d4d9cc - Diamond light
let dark_theme = {
# color for nushell primitives
separator: "#d0d9d7"
leading_trailing_space_bg: { attr: n } # no fg, no bg, attr none effectively turns this off
header: { fg: "#257d10" attr: b }
empty: "#00327b"
# Closures can be used to choose colors for specific values.
# The value (in this case, a bool) is piped into the closure.
# eg) {|| if $in { 'light_cyan' } else { 'light_gray' } }
bool: "#3b96bf"
int: "#d0d9d7"
filesize: "#3b96bf"
duration: "#d0d9d7"
date: "#703b6c"
range: "#d0d9d7"
float: "#d0d9d7"
string: "#d0d9d7"
nothing: "#d0d9d7"
binary: "#d0d9d7"
cell-path: "#d0d9d7"
row_index: { fg: "#257d10" attr: b }
record: "#d0d9d7"
list: "#d0d9d7"
block: "#d0d9d7"
hints: "#257d10"
search_result: { bg: "#c02310" fg: "#d0d9d7" }
shape_and: { fg: "#703b6c" attr: b }
shape_binary: { fg: "#703b6c" attr: b }
shape_block: { fg: "#00327b" attr: b }
shape_bool: "#3b96bf"
shape_closure: { fg: "#257d10" attr: b }
shape_custom: "#257d10"
shape_datetime: { fg: "#3b96bf" attr: b }
shape_directory: "#3b96bf"
shape_external: "#3b96bf"
shape_externalarg: { fg: "#257d10" attr: b }
shape_external_resolved: { fg: "#bc7800" attr: b }
shape_filepath: "#3b96bf"
shape_flag: { fg: "#00327b" attr: b }
shape_float: { fg: "#703b6c" attr: b }
# shapes are used to change the cli syntax highlighting
shape_garbage: { fg: "#d0d9d7" bg: "#c02310" attr: b }
shape_globpattern: { fg: "#3b96bf" attr: b }
shape_int: { fg: "#703b6c" attr: b }
shape_internalcall: { fg: "#3b96bf" attr: b }
shape_keyword: { fg: "#3b96bf" attr: b }
shape_list: { fg: "#3b96bf" attr: b }
shape_literal: "#00327b"
shape_match_pattern: "#257d10"
shape_matching_brackets: { attr: u }
shape_nothing: "#3b96bf"
shape_operator: "#bc7800"
shape_or: { fg: "#703b6c" attr: b }
shape_pipe: { fg: "#703b6c" attr: b }
shape_range: { fg: "#bc7800" attr: b }
shape_record: { fg: "#3b96bf" attr: b }
shape_redirection: { fg: "#703b6c" attr: b }
shape_signature: { fg: "#257d10" attr: b }
shape_string: "#257d10"
shape_string_interpolation: { fg: "#3b96bf" attr: b }
shape_table: { fg: "#00327b" attr: b }
shape_variable: "#703b6c"
shape_vardecl: "#703b6c"
}
Apart from this section, I modified colours in some other sections.
Here is how it looks after modification.
You can find my Nushell config file in prs-configs repository.
Summary
Nushell has over 500 build in commands that works this way. It's a lot of things to learn. But it's pretty exciting after spending a long time with classic Linux tools.
Nu is also a programming language.
Nushell is in active development state. It has the weekly newsletter "This week in Nushell". You can also find the latest news on blog and @nu_shell.