Introduction
Introduction Statistics Contact Development Disclaimer Help
Title: Nushell: Introduction to a new kind of shell
Author: Solène
Date: 31 October 2022
Tags: openbsd nixos nushell shell
Description: This article covers a gentle introduction to the shell
nushell
# What is nushell
Let me introduce you to a nice project I found while lurking on the
Internet. It's called nushell and is a non-POSIX shell, so most of
your regular shells knowledge (zsh, bash, ksh, etc…) can't be applied
on it, and using it feels like doing functional programming.
It's a good tool for creating robust data manipulation pipelines, you
can think of it like a mix of a shell which would include awk's power,
behave like a SQL database, and which knows how to import/export
XML/JSON/YAML/TOML natively.
You may want to try nushell only as a tool, and not as your main shell,
it's perfectly fine.
With a regular shell, iterating over a command output can be complex
when it involves spaces or newlines, for instance, that's why `find`
and `xargs` have a `-print0` parameter to have a special delimited
between "items", but it doesn't compose well with other tools. Nushell
handles correctly this situation as its manipulates the data using
indexed entries, given you correctly parsed the input at the beginning.
Nushell official project page
Nushell documentation website
# How to get it
Nushell is a rust program, so it should work on every platform where
Rust/Cargo are supported. I packaged it for OpenBSD, so it's available
on -current (and will be in releases after 7.3 is out), the port could
be used on 7.2 with no effort.
With Nix, it's packaged under the name `nushell`, the binary name is
`nu`.
For other platforms, it's certainly already packaged, otherwise you can
find installation instructions to build it from sources.
Nushell documentation: Building nushell from sources
# Configuration
At first run, you are prompted to use default configuration files, I'd
recommend accepting, you will have files created in
`~/.config/nushell/`.
The only change I made from now is to make Tab completion
case-sensitive, so `D[TAB]` completes to `Downloads` instead of asking
between `dev` and `Downloads`. Look for `case_sensitive_completions` in
`.config/nushell/config.nu` and set it to `true`.
# Examples
If you are like me, and you prefer learning by doing instead of reading
a lot of documentation, I prepared a bunch of real world use case you
can experiment with. The documentation is still required to learn the
many commands and syntax, but examples are a nice introduction.
## Getting help
Help from nushell can be parsed directly with nu commands, it's
important to understand where to find information about commands.
Use `help a-command` to learn from a single command:
```script
> help help
Display help information about commands.
Usage:
> help {flags} ...(rest)
Flags:
-h, --help - Display this help message
-f, --find <String> - string to find in command names, usage, and search terms
[cut so it's not too long]
```
Use `help commands` to list all available commands (I'm limiting to 5
between there are a lot of commands)
```script
help commands | last 5
╭───┬─────────────┬───────�…
│ # │ name │ category │ is_plugin │ is_custom �…
├───┼─────────────┼───────�…
│ 0 │ window │ filters │ false │ false �…
│ 1 │ with-column │ dataframe or lazyframe │ false │ false �…
│ 2 │ with-env │ env │ false │ false �…
│ 3 │ wrap │ filters │ false │ false �…
│ 4 │ zip │ filters │ false │ false �…
╰───┴─────────────┴───────�…
```
Add `sort-by category` to list them... sorted by category.
```
help commands | sort-by category
```
Use `where category == filters` to only list commands from the
`filters` category.
```
help commands | where category == filters
```
Use `find foobar` to return lines containing `foobar`.
```
help commands | find insert
```
## General examples
### Converting a data structure into another
This is just an example from YAML to JSON, but you can convert much
more formats into other formats.
```
open dev/home-impermanence/tests/impermanence.yml | to json
{
"directories":
[
"Documents",
"Downloads",
"Datastore/Music",
"Datastore",
"Datastore/",
"Datastore/Music/Band1",
".config",
"foo/bar",
"foo/bar/hello"
],
"size": "500m",
"files":
[
".Xdefaults",
".profile",
".xsession",
]
}
```
### Parsing sysctl output
```
sysctl -a | parse -r "(?<key>.*?)=(?<value>.*)"
```
Because the output would be too long, here is how you get 10 random
keys from sysctl.
```
sysctl -a | parse -r "(?<key>.*?)=(?<value>.*)" | shuffle | last 10 | sort-by k…
╭───┬─────────────────────�…
│ # │ key │ value │
├───┼─────────────────────�…
│ 0 │ fs.quota.reads │ 0 │
│ 1 │ net.core.high_order_alloc_disable │ 0 │
│ 2 │ net.ipv4.conf.all.drop_gratuitous_arp │ 0 │
│ 3 │ net.ipv4.conf.default.rp_filter │ 2 │
│ 4 │ net.ipv4.conf.lo.disable_xfrm │ 1 │
│ 5 │ net.ipv4.conf.lo.forwarding │ 0 │
│ 6 │ net.ipv4.ipfrag_low_thresh │ 3145728 │
│ 7 │ net.ipv6.conf.all.ioam6_id │ 65535 │
│ 8 │ net.ipv6.conf.all.router_solicitation_interval │ 4 │
│ 9 │ net.mptcp.enabled │ 1 │
╰───┴─────────────────────�…
```
### Recursively convert FLAC files to OPUS
A complicated task using a regular shell, recursively find files
matching a pattern and then run a given command on each of them, in
parallel. Which is exactly what you need if you want to convert your
music library into another format, let's convert everything from FLAC
to OPUS in this example.
In the following command line, we will look for every `.flac` file in
the subdirectories, then run in parallel using `par-each` the command
`ffmpeg` on it, from its current name to the old name with `.flac`
changed to `.opus`.
The `let convert` and `| complete` commands are used to store the
output of each command into a result table, and store it in the
variable `convert` so we can query it after the job is done.
```
let convert = (ls **/*flac | par-each { |file| do -i { ffmpeg -i $file.name ($f…
```
Now, we have a structure in `convert` that contains the columns
`stdout`, `stderr` and `exit_code`, so we can look if all the commands
did run correctly using the following query.
```
$convert | where exit_code != 0
```
### Synchronize a music library to a compressed one
I had a special need for my phone and my huge music library, I wanted
to have a lower quality version of it synced with syncthing, but I
needed this to be easy to update when adding new files.
It takes all the music files in `/home/user/Music/` and creates a 64K
opus file in `/home/user/Stream/` by keeping the same file tree
hierarchy, and if the opus destination file exists it's skipped.
```nushell
cd /home/user/Music/
let dest = "/home/user/Stream/"
let convert = (ls **/* |
where name =~ ".(mp3|flac|opus|ogg)$" |
where name !~ "(Audiobook|Piano)" |
par-each {
|file| do -i {
let new_name = ($file.name | str replac…
if (not ([$dest, $new_name] | str join …
mkdir ([$dest, ($file.name | pa…
ffmpeg -i $file.name -b:a 64K (…
} | complete
}
})
$convert
```
### Convert PDF/CBR/CBZ pages into webp and CBZ archives
I have a lot of digitalized books/mangas/comics, this conversion is a
handy operation reducing the size of the files by 40% (up to 70%).
```
def conv [] {
if (ls | first | get name | str contains ".jpg") {
ls *jpg | par-each {|file| do -i { cwebp $file.name -o ($file.name | …
rm *jpg
}
if (ls | first | get name | str contains ".ppm") {
ls *ppm | par-each {|file| do -i { cwebp $file.name -o ($file.name | …
rm *ppm
}
}
ls * | each {|file| do -i {
if ($file.name | str contains ".cbz") { unzip $file.name -d ./pages/ } ;
if ($file.name | str contains ".cbr") { unrar e -op./pages/ $file.name …
if ($file.name | str contains ".pdf") { mkdir pages ; pdfimages $file.n…
cd pages ; conv ; cd ../ ; ^zip -r $"($file.name).webp.cbz" pages ; rm …
} }
```
### Parse gnu tar output
```
〉tar vtf nushell.tgz | parse -r "(.*?) (.*?)\/(.*?)\\s+(.*?) (.*?) (.*?) (.*…
╭───┬────────────┬────────�…
│ # │ mode │ owner │ group │ size │ date │ time �…
├───┼────────────┼────────�…
│ 0 │ drwxr-xr-x │ solene │ wheel │ 0 │ 2022-10-30 │ 16:45 �…
│ 1 │ -rw-r--r-- │ solene │ wheel │ 519 │ 2022-10-30 │ 13:41 �…
│ 2 │ -rw-r--r-- │ solene │ wheel │ 29304 │ 2022-10-29 │ 18:49 �…
│ 3 │ -rw-r--r-- │ solene │ wheel │ 75003 │ 2022-10-29 │ 13:16 �…
│ 4 │ drwxr-xr-x │ solene │ wheel │ 0 │ 2022-10-30 │ 00:00 �…
│ 5 │ -rw-r--r-- │ solene │ wheel │ 337 │ 2022-10-29 │ 18:52 �…
│ 6 │ -rw-r--r-- │ solene │ wheel │ 14 │ 2022-10-29 │ 18:53 �…
╰───┴────────────┴────────�…
```
### Opening spreadsheets
```
〉open --raw freq.ods | from ods | get Sheet1 | headers
╭───┬─────────────┬───────�…
│ # │ Policy │ Compile time │ Idle time │ column3 │ Compile po…
├───┼─────────────┼───────�…
│ 0 │ powersaving │ 1123.00 │ 0.00 │ │ 5…
│ 1 │ auto │ 871.00 │ 252.00 │ │ 5…
╰───┴─────────────┴───────�…
```
We can format new strings from columns values.
```
〉open --raw freq.ods | from ods | get Sheet1 | headers | each {|row| do { ech…
╭───┬─────────────────────�…
│ 0 │ powersaving = 5.9 Watts │
│ 1 │ auto = 6.34 Watts │
╰───┴─────────────────────�…
```
### Filter and sort a JSON
There is a website listing packages that can be updated on OpenBSD at
https://portroach.openbsd.org, it provides json of data for rendering.
We can use this data to sort which maintainer has the most up to date
percentage, but only if they manage more than 30 packages.
```
fetch https://portroach.openbsd.org/json/totals.json | get results | where tota…
```
## NixOS examples
### Query profiles packages
```
nix profile list | parse "{index} {flake} {source} {store}"
╭───┬─────────────────────�…
│ # │ flake │ …
├───┼─────────────────────�…
│ 0 │ flake:nixpkgs#legacyPackages.x86_64-linux.libreoffice │ path:/nix/s…
│ │ │ narHash=sha…
│ │ │ b5e2a934894…
│ 1 │ flake:nixpkgs#legacyPackages.x86_64-linux.dino │ path:/nix/s…
│ │ │ narHash=sha…
│ │ │ 4afca5fd9f5…
╰───┴─────────────────────�…
```
### Query flakes
```
nix flake show --json | from json
╭────────────────┬────────�…
│ defaultPackage │ {record 5 fields} │
│ packages │ {record 5 fields} │
╰────────────────┴────────�…
nix flake show --json | from json | get packages
╭────────────────┬────────�…
│ aarch64-darwin │ {record 2 fields} │
│ aarch64-linux │ {record 2 fields} │
│ i686-linux │ {record 2 fields} │
│ x86_64-darwin │ {record 2 fields} │
│ x86_64-linux │ {record 2 fields} │
╰────────────────┴────────�…
nix flake show --json | from json | get packages.x86_64-linux
╭───────────────┬─────────�…
│ nix-dev-html │ {record 2 fields} │
│ nix-dev-pyenv │ {record 3 fields} │
╰───────────────┴─────────�…
```
### Parse a flake.lock file
```
> open flake.lock | from json | get nodes.nixpkgs.locked
╭──────────────┬──────────�…
│ lastModified │ 1663494472 │
│ narHash │ sha256-fSowlaoXXWcAM8m9wA6u+eTJJtvruYHMA+Lb/tFi/qM= │
│ path │ /nix/store/iw3xi0bfszikb0dmyywp7pm590jvbqvs-source │
│ rev │ f677051b8dc0b5e2a9348941c99eea8c4b0ff28f │
│ type │ path │
╰──────────────┴──────────�…
```
## OpenBSD examples
### Parse /etc/fstab
```
> open /etc/fstab | from ssv -m 1 -n | rename device mountpoint fs options freq…
_────┬────────────────────┬…
│ # │ device │ mountpoint │ fs │ …
├────┼────────────────────�…
│ 0 │ 55a6c21017f858cb.b │ none │ swap │ sw …
│ 1 │ 55a6c21017f858cb.a │ / │ ffs │ rw,noatime,softd…
│ 2 │ 55a6c21017f858cb.l │ /home │ ffs │ rw,noatime,wxall…
│ 3 │ 55a6c21017f858cb.d │ /tmp │ ffs │ rw,noatime,softd…
│ 4 │ 55a6c21017f858cb.f │ /usr │ ffs │ rw,noatime,softd…
│ 5 │ 55a6c21017f858cb.g │ /usr/X11R6 │ ffs │ rw,noatime,softd…
│ 6 │ 55a6c21017f858cb.h │ /usr/local │ ffs │ rw,noatime,softd…
│ 7 │ 55a6c21017f858cb.k │ /usr/obj │ ffs │ rw,noatime,softd…
│ 8 │ 55a6c21017f858cb.j │ /usr/src │ ffs │ rw,noatime,softd…
│ 9 │ 55a6c21017f858cb.e │ /var │ ffs │ rw,noatime,softd…
│ 10 │ afebb2a83a449265.b │ /build │ ffs │ rw,noatime,softd…
│ 11 │ afebb2a83a449265.a │ /build/pobj │ ffs │ rw,noatime,softd…
│ 12 │ 55a6c21017f858cb.b │ /build/pobj_mfs │ mfs │ -s1G,wxallowed,n…
╰────┴────────────────────�…
```
### Parse /var/log/messages
```
open /var/log/messages | parse -r "(?<date>\\w+ \\d+ \\d+:\\d+:\\d+) (?<hostnam…
╭───┬─────────────────┬───�…
│ # │ date │ hostname │ program │ pid │ …
├───┼─────────────────┼───�…
│ 0 │ Oct 31 10:27:32 │ fx6 │ collectd │ 55258 │ uc_update: …
│ 1 │ Oct 31 10:43:02 │ fx6 │ collectd │ 55258 │ uc_update: …
│ 2 │ Oct 31 11:00:01 │ fx6 │ syslogd │ 4629 │ restart …
│ 3 │ Oct 31 11:05:26 │ fx6 │ pkg_delete │ │ Removed hel…
│ 4 │ Oct 31 11:05:29 │ fx6 │ pkg_add │ │ Added helix…
│ 5 │ Oct 31 11:16:49 │ fx6 │ pkg_add │ │ Added llvm-…
│ 6 │ Oct 31 11:20:18 │ fx6 │ pkg_add │ │ Added clang…
│ 7 │ Oct 31 11:20:32 │ fx6 │ pkg_add │ │ Added bash-…
│ 8 │ Oct 31 11:20:34 │ fx6 │ pkg_add │ │ Added fzf-0…
│ 9 │ Oct 31 11:21:01 │ fx6 │ pkg_delete │ │ Removed fzf…
╰───┴─────────────────┴───�…
```
### Parse pkg_info output
```
pkg_info | str trim | parse -r "(?<package>.*?)-(?<version>[a-zA-Z0-9\\.]*?) (…
╭────┬───────────────────┬�…
│ # │ package │ version │ description …
├────┼───────────────────┼�…
│ 0 │ athn-firmware │ 1.1p4 │ firmware binary images for athn…
│ 1 │ collectd │ 5.12.0 │ system metrics collection engin…
│ 2 │ curl │ 7.85.0 │ transfer files with FTP, HTTP, …
│ 3 │ gettext-runtime │ 0.21p1 │ GNU gettext runtime libraries a…
│ 4 │ intel-firmware │ 20220809v0 │ microcode update binaries for I…
│ 5 │ inteldrm-firmware │ 20220913 │ firmware binary images for inte…
│ 6 │ kakoune │ 2021.11.08 │ modal code editor with a focus …
│ 7 │ libgcrypt │ 1.10.1p0 │ crypto library based on code us…
│ 8 │ libgpg-error │ 1.46 │ error codes for GnuPG related s…
│ 9 │ libiconv │ 1.17 │ character set conversion librar…
│ 10 │ libstatgrab │ 0.91p5 │ system statistics gathering lib…
│ 11 │ libxml │ 2.10.3 │ XML parsing library …
│ 12 │ libyajl │ 2.1.0 │ small JSON library written in A…
│ 13 │ nghttp2 │ 1.50.0 │ library for HTTP/2 …
│ 14 │ nushell │ 0.70.0 │ a new kind of shell …
│ 15 │ obsdfreqd │ 1.0.3 │ userland daemon to manage CPU f…
│ 16 │ quirks │ 6.42 │ exceptions to pkg_add rules and…
│ 17 │ rsync │ 3.2.5pl0 │ mirroring/synchronization over …
│ 18 │ ttyplot │ 1.4p0 │ realtime plotting utility for t…
│ 19 │ vmm-firmware │ 1.14.0p0 │ firmware binary images for vmm(…
│ 20 │ xz │ 5.2.7 │ LZMA compression and decompress…
│ 21 │ yash │ 2.52 │ POSIX-compliant command line sh…
╰────┴───────────────────┴�…
```
# Conclusion
Nushell is very fun, it's terribly different from regular shells, but
it comes with a powerful language and tooling. I always liked shells
because of pipes commands, allowing to construct a complex
transformation/analysis step by step, and easily inspect any step, or
be able to replace a step by another.
With nushell, it feels like I finally have a better tool to create more
reliable, robust, portable and faster command pipelines. The learning
curve didn't feel too hard, but maybe it's because I'm already used to
functional programming.
You are viewing proxied material from dataswamp.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.