Converting Command Line Output to JSON with Nushell
Nushell
Nushell is pretty great.
It’s a new shell that has as its biggest selling point the fact that pipes are inherently structured.
I’ve been playing around with version 0.13.0 and was able to find a few rough edges. But I really will be using this in the future.
Often, I am torn between writing scripts in Bash or Python. Python is obviously much saner for bigger tasks. But Bash operates nicely with the POSIX world and pipes are just great. Nushell looks like a great replacement of Bash (or equivalent) where data is not just raw lines of text.
Converting Output
Last week, I learned off sshping. It’s a small utility that measures SSH statistics (latency, bandwidth). However, its output looks like this:
❯ sshping -H chris@100.68.54.35
ssh-Login-Time: 1.77 s
Minimum-Latency: 3.71 ms
Median-Latency: 5.58 ms
Average-Latency: 6.03 ms
Average-Deviation: 1.65 ms
Maximum-Latency: 34.6 ms
Echo-Count: 1.00 kB
Upload-Size: 8.00 MB
Upload-Rate: 4.48 MB/s
Download-Size: 8.00 MB
Download-Rate: 4.23 MB/s
In other words, this is a manually constructed table. Not really useful if you want to store it for future analysis.
Nushell is made for handling structured data. But it can also create structured data.
Let me show you how I parse the sshping output to JSON. I will build this up, piece by piece. To speed up the individual steps, I first save the result of sshping into a file. This is not necessary if we are only running the final command once. In case you are wondering, the IP is a NUC machine connected via Tailscale.
sshping -H chris@100.68.54.35 | save --raw ping.raw
The save
command is the equivalent of redirecting into a file in Bash (or
other POSIX shells). In the same way, there is also open
. Note that this is
just raw data and note that there are no rows or columns yet.
> open ping.raw
ssh-Login-Time: 1.81 s
Minimum-Latency: 3.14 ms
Median-Latency: 5.82 ms
Average-Latency: 9.26 ms
Average-Deviation: 21.0 ms
Maximum-Latency: 210 ms
Echo-Count: 1.00 kB
Upload-Size: 8.00 MB
Upload-Rate: 3.22 MB/s
Download-Size: 8.00 MB
Download-Rate: 4.82 MB/s
lines
will turn each raw line into one row in our data. Each row is numbered:
> open ping.raw | lines
────┬────────────────────────────────────────
# │ <value>
────┼────────────────────────────────────────
0 │ ssh-Login-Time: 1.81 s
1 │ Minimum-Latency: 3.14 ms
2 │ Median-Latency: 5.82 ms
3 │ Average-Latency: 9.26 ms
4 │ Average-Deviation: 21.0 ms
5 │ Maximum-Latency: 210 ms
6 │ Echo-Count: 1.00 kB
7 │ Upload-Size: 8.00 MB
8 │ Upload-Rate: 3.22 MB/s
9 │ Download-Size: 8.00 MB
10 │ Download-Rate: 4.82 MB/s
────┴────────────────────────────────────────
Next, we turn the rows into columns with the split-column
command. This could
also be done with the parse
command like this: parse {metric}: {value}
. Both
will give us the same result in this case.
> open ping.raw | lines | split-column ':' metric value
────┬───────────────────┬────────────────────────────
# │ metric │ value
────┼───────────────────┼────────────────────────────
0 │ ssh-Login-Time │ 1.81 s
1 │ Minimum-Latency │ 3.14 ms
2 │ Median-Latency │ 5.82 ms
3 │ Average-Latency │ 9.26 ms
4 │ Average-Deviation │ 21.0 ms
5 │ Maximum-Latency │ 210 ms
6 │ Echo-Count │ 1.00 kB
7 │ Upload-Size │ 8.00 MB
8 │ Upload-Rate │ 3.22 MB/s
9 │ Download-Size │ 8.00 MB
10 │ Download-Rate │ 4.82 MB/s
────┴───────────────────┴────────────────────────────
Note that the value
column has a lot of whitespace that we definitely do not
want. find-replace
in the str
command will help. (NOTE: In the future trim
will probably work on cells and be better suited for this job. Version 0.13.0
does not have the fix yet.)
NOTE: See the Update below, str value --trim
will only be available in Nu
0.14.0. Replace --trim
with --find-replace [\W+ '']
as long as 0.14.0 has
not been released.
> open ping.raw | lines | split-column ':' metric value | str value --trim
────┬───────────────────┬───────────
# │ metric │ value
────┼───────────────────┼───────────
0 │ ssh-Login-Time │ 1.81 s
1 │ Minimum-Latency │ 3.14 ms
2 │ Median-Latency │ 5.82 ms
3 │ Average-Latency │ 9.26 ms
4 │ Average-Deviation │ 21.0 ms
5 │ Maximum-Latency │ 210 ms
6 │ Echo-Count │ 1.00 kB
7 │ Upload-Size │ 8.00 MB
8 │ Upload-Rate │ 3.22 MB/s
9 │ Download-Size │ 8.00 MB
10 │ Download-Rate │ 4.82 MB/s
────┴───────────────────┴───────────
And now we can use the save command to save this into the format of our choosing. The file extension determines the format.
> open ping.raw | lines | split-column ':' metric value | str value --trim | save ping.json
Wishlist
save
should allow pretty-formatting of JSON.trim
should work on cells not on lines. This was already suggested by one of the Nushell authors, Jonathan Turner, on Discord.
Update [2020-04-22]
A str --trim
option was added a few hours after I published this by
Andrés Robalino. Before the --trim
, I had to use
--find-replace [\W+ '']
Follow-up
Please see my follow up post Nushell vs Bash!