Shell Cacophony

I am using jq, qsv, uplot quite often. This post is to make sure that you know and use them, too. I hope you will waste as much time as I do, especially with uplot.

Motivation

This post is for people like me who are facing a problem, reaching out to the terminal and piping random UNIX commands to solve it, but eventually regret not starting with Python or some other programming language in the first place.

I just want to make sure that we have some more shell goodies in our toolbox so that we keep doing the same thing over and over again: jq, qsv, uplot.

jq

I am pretty sure that you have heard about jq before, and most of you are already using it. But for those who are not familiar with it, jq is a lightweight and command-line JSON processor, sort of like sed for JSON data.

For example, use xColors API to generate 10 random colors and extract the hex codes to print them:

curl -s "https://x-colors.yurace.pro/api/random?number=10" |
  jq -r ".[]|.hex"

qsv

qsv is a command-line tool to work with CSV files. It is the successor of xsv and is written in Rust. Current progress is quite impressive as qsv now has SQL and Lua support.

For example, if we want to tabulate bird emojis from EmojiHub API:

curl -s "https://emojihub.yurace.pro/api/all/group/animal-bird" |
  jq -r ".[]|[.name,.category,.group,.unicode[0]]|@csv" |
  qsv rename --no-headers name,category,group,unicode |
  qsv table

Here, we are using jq to extract the necessary fields and output records as CSV. Then, we are using qsv to add column names and finally tabulate.

uplot

uplot is a command-line tool to plot data on the terminal. Be warned that it is quite addictive.

Let’s check the number of important (p <= 3) journald entries per day over the last 1 week:

journalctl --no-pager --since="1 week ago" --priority=3 --output=json --output-fields=__REALTIME_TIMESTAMP |
  jq -r .__REALTIME_TIMESTAMP |
  cut -c 1-10 |
  xargs -I{} date --utc -d @{} +%Y-%m-%d |
  uniq -c |
  awk '{print $2","$1}' |
  uplot bar -d,

Here, we are using journalctl to get the entries with priority less than or equal to 3 in JSON format with realtime timestamp information only. Then, we are using jq to extract the timestamp, cut to get the date first 10 digits of the timestamp (in microseconds), xargs to convert the timestamp to date format using date, uniq to count the number of entries per day, awk to format the output as CSV, and finally uplot to plot the data.

The output looks like this (trust me, it will look much nicer and colourful on your terminal):

           ┌                                        ┐
2024-08-22 ┤■ 1.0
2024-08-23 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 44.0
2024-08-24 ┤■■■■■ 8.0
2024-08-25 ┤■■■■■■■■■■■■■■■■■■■■■ 33.0
2024-08-26 ┤■■■■■■■■■■■■■■■■ 25.0
2024-08-27 ┤■■■■■■■■■■■■ 18.0
2024-08-28 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 53.0
2024-08-29 ┤■■■■■■ 10.0
           └                                        ┘

Likewise, we can get the number of entries by unit since yesterday:

journalctl --no-pager --since=yesterday --priority=3 --output=json --output-fields=_SYSTEMD_UNIT |
  jq -r ._SYSTEMD_UNIT |
  sort |
  uniq -c |
  awk '{print $2","$1}' |
  uplot bar -d,

… which produces the following completely unsurprising output:

                                    ┌                                        ┐
                  bluetooth.service ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 58.0
                       cups.service ┤■ 1.0
                               null ┤■■ 4.0
systemd-coredump@0-378352-0.service ┤■ 1.0
                                    └                                        ┘

Conclusion

I hope you find these tools useful.

Closing with our motto: Stick to the terminal and keep piping random UNIX commands to (not) solve our problems!

Bonus

qsv is currently not available on nixpkgs. So, I fixed it for you:

{ stdenv
, lib
, fetchzip
, autoPatchelfHook
}:

stdenv.mkDerivation rec {
  pname = "qsv";
  version = "0.132.0";

  src = fetchzip {
    url = "https://github.com/jqnatividad/qsv/releases/download/${version}/qsv-${version}-x86_64-unknown-linux-gnu.zip";
    hash = "sha256-yko+wTSGxOZWU1cJS17sPYPQeBcfyeiwQUu6dPhpL1s=";
    stripRoot = false;
  };

  nativeBuildInputs = [
    autoPatchelfHook
    stdenv.cc.cc.lib
  ];

  buildInputs = [ ];

  sourceRoot = ".";

  installPhase = ''
    runHook preInstall
    install -m755 -D source/qsvp $out/bin/qsv
    runHook postInstall
  '';

  meta = with lib; {
    homepage = "https://github.com/jqnatividad/qsv";
    description = "CSVs sliced, diced & analyzed.";
    platforms = platforms.linux;
  };
}
Published on 29 August 2024 Technical Note Unix Hacking Computing