NuShell: Shell Redefined

NuShell is an alternative shell. It has many features that are pretty useful such as table as output, filtering the columns and rows or searching them. That's why it is safe to say that it's not Bash compatible but being quite useful for what it currently is. It is actually written in Rust, which is a technology that I am interested in. I tried to give it a try a couple of days ago and wanted to introduce it to you.

NuShell is cross-platform, you can install and use it in your Windows, Linux or Mac machine. In Linux, you can use your package system to install it. In other operating systems, you can see their installation page to see how to install it in your machine.


Features

I would first like to give you a brief introduction to its key features.

Data Oriented Output and Output as Table

In NuShell, output as data is first-class citizen. The best way to represent data in a terminal is to show it as a table. For instance, doing ls will print the current directory as a table.

 > ls
───┬─────────┬──────┬──────┬─────────────
 # │ nametypesizemodified 
───┼─────────┼──────┼──────┼─────────────
 0 │ bar.pngFile │  0 B │ 4 secs ago 
 1 │ baz.txtFile │  0 B │ 1 sec ago 
 2 │ fooDir  │      │ 12 secs ago 
───┴─────────┴──────┴──────┴─────────────

And pretty much many commands output as table like this one.

NuShell is also kind of object oriented. You can access embedded objects inside other objects. For example, see what sys does:

 > sys
───────┬─────────────────────────────────────────
 host[row 7 columns] 
 cpu[row cores current ghz max ghz min ghz] 
 disks[table 6 rows] 
 mem[row free swap free swap total total] 
 temp[table 4 rows] 
 net[table 2 rows] 
───────┴─────────────────────────────────────────

sys is an internal command and returns an object that is viewed as a table. You can access its internal properties with get. See below:

 > sys | get host
──────────┬─────────────────────────────────────
 name     │ Linux 
 release  │ 4.19.117-1-MANJARO 
 version  │ #1 SMP Tue Apr 21 12:19:11 UTC 2020 
 hostname │ eray-pc 
 arch     │ x86_64 
 uptime   │ 6:53:59 
 sessions │ [table 3 rows] 
──────────┴─────────────────────────────────────

You can also get deeper:

 > sys | get host.release
 4.19.117-1-MANJARO

Pipeline and Sort & Filter

As a Bash user, using pipes (| character) is vital part of a development workflow and often used to process the output, whether it is to filter them, search them or redirect them. Since NuShell is data oriented, you can pretty much do these with its internal commands combining with pipes. For instance, say we only care about name and type, we can do:

 > ls | pick name type
───┬─────────┬──────
 # │ name    │ type 
───┼─────────┼──────
 0 │ bar.png │ File 
 1 │ baz.txt │ File 
 2 │ foo     │ Dir 
───┴─────────┴──────

Assuming it is a long output, we can define it to get only to an extent as below:

 > ls | first 2
───┬─────────┬──────┬──────┬────────────
 # │ name    │ type │ size │ modified 
───┼─────────┼──────┼──────┼────────────
 0 │ bar.png │ File │  0 B │ 4 mins ago 
 1 │ baz.txt │ File │  0 B │ 4 mins ago 
───┴─────────┴──────┴──────┴────────────

Examples until now may not probably seem quite functional to you. NuShell has more. You can sort it by any column you'd like as below:

 > ls | sort-by modified
───┬─────────┬──────┬──────┬────────────
 # │ name    │ type │ size │ modified 
───┼─────────┼──────┼──────┼────────────
 0 │ foo     │ Dir  │      │ 5 mins ago 
 1 │ bar.png │ File │  0 B │ 5 mins ago 
 2 │ baz.txt │ File │  0 B │ 5 mins ago 
───┴─────────┴──────┴──────┴────────────

You can filter by column:

 > ls | where type == File
───┬─────────┬──────┬──────┬────────────
 # │ name    │ type │ size │ modified 
───┼─────────┼──────┼──────┼────────────
 0 │ bar.png │ File │  0 B │ 7 mins ago 
 1 │ baz.txt │ File │  0 B │ 7 mins ago 
───┴─────────┴──────┴──────┴────────────

Useful IO Operations

NuShell provides useful internal commands while reading from the disk or from the network. One of them is open. This is like cat but better.

 > open baz.txt
 lorem ipsum

This is a simple text file. However, it gets even more useful when reading a data file we are probably familiar with, such as JSON, TOML or YAML files. For instance, see below for a JSON file:

 > open me.json
──────────┬────────────────
 nameEray 
 surnameErdin 
 contacts[table 2 rows] 
──────────┴────────────────

This is a JSON file, containing name, surname and contacts keys. name and surname are strings and contacts is an array of two objects. You can combine these with what you've seen above, such as:

 > open me.json | get contacts
───┬────────┬──────────
 # │ name   │ surname 
───┼────────┼──────────
 0 │ Ruslan │ Hasanov 
 1 │ Melih  │ Yıldırım 
───┴────────┴──────────

 > open me.json | get contacts | sort-by name
───┬────────┬──────────
 # │ name   │ surname 
───┼────────┼──────────
 0 │ Melih  │ Yıldırım 
 1 │ Ruslan │ Hasanov 
───┴────────┴──────────

open allows you to work with your local operations. And there is also fetch, where you can do pretty much the same things with an API. For instance:

 > fetch "https://jsonplaceholder.typicode.com/posts/1"
────────┬────────────────────────────────────────────────────────────────────────────
 userId │ 1 
 id1 
 title  │ sunt aut facere repellat provident occaecati excepturi optio reprehenderit 
 body   │ quia et suscipit 
        │ suscipit recusandae consequuntur expedita et cum 
        │ reprehenderit molestiae ut ut quas totam 
        │ nostrum rerum est autem sunt rem eveniet architecto 
────────┴────────────────────────────────────────────────────────────────────────────

And use many commands with pipes:

 > fetch "https://jsonplaceholder.typicode.com/posts" | where userId == 1 | pick id title
───┬────┬────────────────────────────────────────────────────────────────────────────
 # │ id │ title 
───┼────┼────────────────────────────────────────────────────────────────────────────
 0 │  1 │ sunt aut facere repellat provident occaecati excepturi optio reprehenderit 
 1 │  2 │ qui est esse 
───┴────┴────────────────────────────────────────────────────────────────────────────

Maybe your data type is not a well-known one and you'd like to quickly parse it and see it readable on your terminal. NuShell solves this problem. Consider we have a text file with content as below:

Eray # Erdin
Ruslan # Hasanov
Melih # Yıldırım

This contains names and surnames, line by line, oddly delimited by pound sign (# sign). We can quickly parse it on NuShell. First, split it to lines:

 > open file.txt | lines
───┬──────────────────
 # │ <value> 
───┼──────────────────
 0 │ Eray # Erdin 
 1 │ Ruslan # Hasanov 
 2 │ Melih # Yıldırım 
───┴──────────────────

Then do split-column:

 > open file.txt | lines | split-column " # "
───┬─────────┬──────────
 # │ Column1 │ Column2 
───┼─────────┼──────────
 0 │ Eray    │ Erdin 
 1 │ Ruslan  │ Hasanov 
 2 │ Melih   │ Yıldırım 
───┴─────────┴──────────

We can also finally name our columns:

 > open file.txt | lines | split-column " # " name surname
───┬────────┬──────────
 # │ name   │ surname 
───┼────────┼──────────
 0 │ Eray   │ Erdin 
 1 │ Ruslan │ Hasanov 
 2 │ Melih  │ Yıldırım 
───┴────────┴──────────

So you can also quickly parse an unknown file like this one.

Small Features

You can do math operations on it:

 > = 2 + 3
5

You can quickly copy the output string with clip:

 > open textfile.txt | clip

You can convert output table to known table types such as CSV, TSV, HTML, Markdown, JSON, TOML, YAML, URL parameters and even SQLite. Assuming you have that unknown typed file with some data inside, just like before and we've done the same thing as before:

 > open file.txt | lines | split-column " # " name surname
───┬────────┬──────────
 # │ name   │ surname 
───┼────────┼──────────
 0 │ Eray   │ Erdin 
 1 │ Ruslan │ Hasanov 
 2 │ Melih  │ Yıldırım 
───┴────────┴──────────

You can convert it as such:

 > open file.txt | lines | split-column " # " name surname | to-csv
name,surname
Eray,"Erdin"
Ruslan,"Hasanov"
Melih,Yıldırım
 > open file.txt | lines | split-column " # " name surname | to-html
<html><body><table><tr><th>name</th><th>surname</th></tr><tr><td>Eray</td><td>Erdin
</td></tr><tr><td>Ruslan</td><td>Hasanov
</td></tr><tr><td>Melih</td><td>Yıldırım</td></tr></table></body></html>
 > open file.txt | lines | split-column " # " name surname | to-json
[{"name":"Eray","surname":"Erdin"},{"name":"Ruslan","surname":"Hasanov"},{"name":"Melih","surname":"Yıldırım"}]

Drawbacks

NuShell is very useful, but there are some things to consider before using it. Despite being a minimum viable product, it is still a very young one. I will talk about the ones I know of. These might not be problem for you, or these might not be a problem if this article is old enough, might have been resolved.

One of these problems (for me) is that the configurations are persistent. NuShell does not have a solid concept of variables in terms of Bash yet. It has the logic of configuration and it persists from session to session. That's why it is not yet possible to set a temporary variable to be used.

Another one is that you cannot really source in your current scope, which renders it impossible to port some of your Bash scripts yet. One example comes to mind is Python's virtualenv. virtualenv uses source heavily in order to set its environment and it's not usable in a NuShell environment yet.

These problems also mean that your development toolkit might not quite integrate well with NuShell very well.


NuShell is an exciting alternative shell. It redefines how a shell should behave from ground up. While it has some considerable drawbacks, I think the benefits outweigh it. That's why today I use NuShell as my default. Yes, Bash has quite much scripts but I can switch to it quickly typing bash anyway.