Coverage

On this page

jz reads output formats, not commands. A command that changes its columns with an option prints two formats and needs two definitions, so “jz supports df” says less than it sounds like. This page says, for the commands asked about most, what each form of their output does when it reaches jz.

Three things can happen, and each of them is an answer:

what it means
readpiping the output in identifies it and converts it
nameduse jz --parser NAME or jz run; some formats also need --variant VARIANT
refusedjz does not read it, and the reason is below

“named” is not a shortcoming. A format whose text could be half a dozen other things is one jz declines to guess at, because the failure worth designing against is confident JSON from the wrong parser rather than exit 4.

Everything below but the Windows section, which names its own machines, was run on Ubuntu 26.04 (Linux 7.0) with LC_ALL=C: GNU coreutils 9.7 and uutils coreutils 0.8.0, procps-ng 4.0.4, util-linux 2.41.3, iproute2 6.19.0, systemd 259, sysstat 12.7, BIND dig 9.20.24, iputils, BusyBox 1.37.0. net-tools was not installed, so the netstat rows below were checked with BusyBox netstat, whose socket and routing tables the existing definitions read, and the interface table was written from the format strings net-tools prints it with rather than captured. The ifconfig, arp -a and rpm rows were checked against captures from containers (net-tools 2.10 on Debian; rpm on Fedora 44, Rocky Linux 8 and 9 and openSUSE Leap 15.6) and from the macOS 15 and 26 runners and a FreeBSD 15.1 virtual machine on GitHub Actions.

Storage and memory #

invocationdefinition
dfreaddf/gnu
df -kreaddf/gnu
df -a, df --totalreaddf/gnu
df -h, df -Hreaddf/gnu-human
df -m, df -B512, df --block-size=Nreaddf/gnu-blocks
df -Treaddf/gnu-type
df -T -mreaddf/gnu-blocks-type
df -ireaddf/gnu-inodes
df -ihreaddf/gnu-inodes-human
df -P, df -PTreaddf/portable, df/portable-type
df --output=...refusedthe columns are whatever the option named; jz --parser table --variant aligned reads it as a table
freereadfree/gnu
free -hreadfree/gnu-human
free -wreadfree/gnu-wide
free -h -wreadfree/gnu-wide-human
free -t, free -l, free -s N -c Nreadthe variant the other options chose
lsblkreadlsblk/linux
lsblk -breadlsblk/bytes
lsblk -freadlsblk/filesystems
lsblk -treadlsblk/topology
lsblk -mreadlsblk/permissions; in a container without the device nodes, owner, group and mode are null together
lsblk -Preadlsblk/pairs
lsblk -rreadlsblk/raw
lsblk -nnamedlsblk/no-headings; with no header the text is a tree drawing and some words
lsblk -Jrefusedit is JSON already
lsblk -o ...refusedthe columns are whatever the option named; --parser table reads it
mount, mount -l, mount -t TYPEreadmount/linux
/proc/mounts, /etc/mtabnamedjz --parser etc reads them as etc/fstab, which is their format
stat FILEreadstat/gnu
stat FILE FILEreadstat/gnu, one object per file
stat -freadstat/gnu-filesystem
stat -tnamedstat/gnu-terse; a row of numbers describes too many things
stat -c, stat --printfrefusedthe format is the one the option gave

A figure df rounds for a person (1.8T) stays a string. The output does not record the base the rounding used and the value is already rounded, so converting it would invent precision the command did not print; run df or df -B1 for exact numbers. Where df counts in blocks, the unit is in the heading and comes out beside the rows.

Processes #

invocationdefinition
psreadps/posix
ps -efreadps/unix
ps auxreadps/bsd
ps -elreadps/long
ps -eFreadps/full-format
ps -elyreadps/long-y
ps -eLfreadps/threads
ps axjf, ps -e --forestreadps/jobs, ps/posix
ps -eo ..., ps -o ...=refusedthe columns are whatever the option named; --parser table reads it
top -b -n1, top -b -n 5 -d 1readtop/linux, a record per iteration
pidstat, pidstat -d, -r, -wreadthe pidstat variants

Listings #

invocationdefinition
ls -lreadls/long
ls -la, -lh, -lt, -lS, -lnreadls/long
ls -l --full-timereadls/full-time
ls -l --time-style=long-isoreadls/long-iso
ls -lZreadls/long-context
ls -lRreadls/long-recursive
ls -log, ls -lgGreadls/long-no-owner-group
ls -lG, ls -lonamedls/long-no-group
ls -lgnamedls/long-no-owner
ls -linamedls/long-inode; ls -ls prints a number in the same place
ls, ls -1namedls/names; a list of lines is not evidence of anything
ls -F, ls -prefusedthe mark after a name is not part of the name and no rule can say which trailing character is one

ls -lG and ls -lg print the same text: one drops the group column and the other the owner, and nothing in the output says which name is left. Both are reached by naming the variant, and jz run ls -lG picks the right one from the arguments. Naming only the command reports both rather than choosing.

Network #

invocationdefinition
ip address, ip -4 address, ip -6 addressreadip/address
ip -br address, ip -o addressreadip/brief-address, ip/oneline-address
ip link, ip -d linkreadip/link
ip -br linkreadip/brief-link
ip -s link, ip -s -s linkreadip/stats-link, ip/stats-link-detail
ip route, ip -6 routereadip/route
ip neigh, ip rule, ip maddrreadthe matching ip variants
ip -j ...refusedit is JSON already
ip tunnel, ip netns, ip xfrmrefusedno definition; the subcommands are each their own format
ss, ss -a, ss -l, ss -x, ss -tuln, ss -preadss/linux
ss -e, ss -o, ss -m, ss -ireadss/linux, with what the option added under info
ss -t, ss -ua, ss -ulreadss/single-protocol
ss -u, ss -wreadss/connected
ss -sreadss/summary
ss -Hrefused-H removes the header, which is the line that says what the format is
netstat, -a, -t, -u, -x, -w, -l, -n, -ereadnetstat/all-sockets, netstat/internet, netstat/unix
netstat -r, netstat -rnreadnetstat/routing
netstat -e -rreadroute/linux, which is the table those columns are
route, route -nreadroute/linux; a rejecting route’s missing counts are null and its gateway and interface stay -
route -ereadnetstat/routing, the table netstat -r prints
route -eereadroute/linux-extended
route -A inet6readroute/linux-inet6
route -Crefusedthe IPv4 routing cache is gone from current kernels, so the table has a header and no rows to write a definition against
netstat -ireadnetstat/interface
netstat -srefusedwhich counters appear, in which order and at which indent comes from the kernel and the net-tools build; there is no capture here to write it against
netstat on Windowsrefusedno definition; no vendor-published text sample was found to write one against, only screenshots
dig NAMEreaddig/bind
dig NAME A NAME MXreaddig/bind, one entry in replies per query
dig +nocmdreaddig/bind, with an empty query
dig +nsid, +dnssec, +tcp, +norecreaddig/bind
a query that reached no serverreaddig/bind, with an empty replies and the attempt under transport
dig axfrreaddig/axfr
dig +noall +answernameddig/answers; the same five columns are what a zone file holds
dig +shortrefusedthe output is the data of the records with nothing naming them
dig +tracerefusedthe delegation steps have no line that opens one, so nothing says where a step ends
ping -c N, -q, -D, -A, -s, -W, ping6readping/linux, ping/bsd
ping -frefusedflood output is a line of dots, not records
ifconfig, ifconfig -a, ifconfig IFACE (net-tools)readifconfig/net-tools
ifconfig (BusyBox)readifconfig/busybox
ifconfig, -a, -v, -L (macOS, FreeBSD)readifconfig/bsd; lines other than the flags, addresses and bit masks are kept in order under properties
ifconfig -s, ifconfig -s -a (net-tools)readifconfig/net-tools-short, the table netstat -i prints without its banner
ifconfig -lrefuseda line of names
arp -a, arp -anreadarp/alternate (Linux), arp/freebsd, arp/darwin, arp/windows
arp, arp -n, arp -e, arp -i IFACE (net-tools)readarp/net-tools
arp -v (net-tools)refusedthe closing count line is not read
tracepathreadtracepath/linux
tracerouterefusedno definition

Packages and history #

invocationdefinition
rpm -qi PACKAGE, rpm -qi A B, rpm -qiareadrpm/info, one object per package; a label rpm did not print is null
rpm -qpi FILE.rpmreadrpm/info; the install date is the text (not installed)
rpm -qi naming a package that is not installedrefusedthe package NAME is not installed line is not part of any block
git log, git log --format=fullerreadgit/log
git log --stat, git log --shortstat, git show --statreadgit/log-stat, with files and summary under each commit
git log --onelinereadgit/log-oneline
git log --numstat, --name-only, --summary, -prefuseda different listing under each commit
git log --format=... with a format of your ownrefusedthe text is whatever the format said, and git does not escape it

systemd #

invocationdefinition
systemctl, systemctl list-units, --all, --plain, --failedreadsystemctl/units
systemctl list-unit-filesreadsystemctl/unit-files
systemctl list-timersreadsystemctl/timers
systemctl list-socketsreadsystemctl/sockets
systemctl list-jobs, including with nothing queuedreadsystemctl/jobs
systemctl list-machinesreadsystemctl/machines
systemctl list-pathsreadsystemctl/paths
systemctl list-automountsreadsystemctl/automounts
systemctl list-dependencies --plainreadsystemctl/dependencies
systemctl status UNITreadsystemctl/status
systemctl show UNITreadsystemctl/show
systemctl shownamedsystemctl/show-properties; NAME=value is also what env prints
systemctl list-dependenciesrefusedwithout --plain a line carries a state marker and a drawing, and a line’s depth then depends on both
systemctl statusrefusedthe whole-system report is a different format with no definition
systemctl list-units --no-legendrefusedthe option removes the header, which is what says the format is this one
systemctl cat UNITrefusedit is the unit file; jz --parser ini reads that
journalctl -o short*readthe journalctl variants

Repeating reports #

invocationdefinition
vmstat, vmstat N M, vmstat -wreadvmstat/linux
vmstat -a, -d, -sreadvmstat/linux-active, -disk, -stats
vmstat -frefusedone line saying how many forks there have been
iostat, iostat N Mreadiostat/linux
iostat -x, -c, -d, -hreadiostat/extended, cpu, device, human
iostat -t and each of those with -treadthe -timestamped variant of each
mpstat, mpstat -Ireadmpstat/linux, mpstat/interrupts
sar -u, -r, -b, -n DEV, …readthe sar variants

Windows #

jz builds and runs on Windows, and that is a different thing from reading what Windows commands print. The rows below were checked against the real commands on the GitHub-hosted Windows Server 2022 and 2025 runners, English, with the console on code page 65001: the end-to-end suite runs each command there on every change and compares what jz reports with hostname, ver and PowerShell on the same machine, and a capture from each release is kept as a fixture beside the output published by the vendor.

invocationdefinition
ipconfig /allreadipconfig/all
ipconfigreadipconfig/windows
systeminfo, systeminfo /fo listreadsysteminfo/windows; /fo list prints the same lines, and jz run refuses it on the argument, since /fo also names the csv and table forms
systeminfo /fo csvnamedjz --parser csv --variant comma
ipconfig /displaydnsrefuseda different format; pinned as a refusal rather than read
netstat on Windowsrefusedno vendor-published text sample was found to write one against, only screenshots
net user, net localgroup, dir, route print, ver, tasklist, wmicrefusedno definition

Not checked, and not claimed: a Windows in another display language, whose labels the signatures do not describe, so its output is refused rather than read; a console code page other than UTF-8, where a name outside ASCII is not valid UTF-8 and the input is refused; and a desktop edition, which no runner provides.

A rounded figure with a separator and a unit (16,384 MB) stays a string here for the reason it does everywhere else: the text does not say which base it was rounded in. A label that holds a colon of its own (Virtual Memory: Max Size:) keeps what follows its first colon as the value, since a list of labels the definition has not seen cannot say which colon is the separator.

What is not read on purpose #

  • Output whose columns are chosen by an option (ps -eo, df --output, lsblk -o, stat -c). There is no format to describe: the caller already knows the columns, and jz --parser table --variant aligned reads an aligned table under whatever headings it has.
  • Output with the header removed (ss -H, systemctl --no-legend, lsblk -n by automatic detection). The header is what says which format the text is; without it jz would be guessing. Leave the option out when piping into jz, or name the parser.
  • Output that is already structured (ip -j, lsblk -J, --format=json anywhere). Converting JSON to JSON is not what this is for.
  • A single word or number (systemctl is-active, id -u). There is nothing to convert.

Compared with jc #

This section compares what the two read. The options, the other differences and timings, together with jo, are on Compared with jc and jo.

jc converts many of the same commands, so it is a second opinion on what a text holds. The comparison below was made against jc 1.25.7 (git 8290734, MIT licence). No jc code or fixture is copied into this repository: the fixtures here were captured or quoted independently, and jc is used as a tool to run against them.

scripts/compare_jc.py runs both over the same saved input and reports, per fixture, whether each produced JSON, how many records, and which words of the input reached a leaf value of each result. It needs jc installed (python3 -m pip install jc) and nothing else in this repository depends on it. It compares against the input rather than between the two documents on purpose: the two name and nest their keys differently, and normalising that away would also hide a value one of them dropped.

--refused turns the harness around and runs it over the fixtures a definition is written to refuse: text of a neighbouring format, a row cut short, a line the definition never looked at. The counts here are those of the run that was made, and the registry has grown since. There were 47 such fixtures with a jc parser to compare against. jz refused 46 of them, jc read 34. The one jz read is wc -lwcL output given to jz --parser wc --variant posix, where the fourth count and a file name beginning with a number are the same text and the arguments are the only thing that separates them; jz run wc -lwcL refuses it on the argument.

Over the 380 fixtures a definition was written to read, jz read all of them and jc refused 30. The refusals are a file system name containing a space (df), every ip address fixture, an ls -l of a directory with nothing in it, the headerless and raw forms of lsblk, most ss forms, pidstat, swapon, an empty zip listing, who with an ISO time, and the systeminfo excerpt.

Where both read a text, the differences worth knowing are these.

  • A rounded number. df -h prints 13G; jc reports 13958643712 and jz reports "13G". The command rounded the value and did not record what it rounded, so the exact figure is not in the text; two rows whose sizes both round to 13G come back from jc as the same number.
  • A value made of parts. dig’s ;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP) is one string in jc and three keys in jz (server, server_name, protocol). Both carry the information.
  • A converted value. jz turns Use% 7% into 7, TIME 00:02:57 into 177 seconds and a timestamp with an offset into RFC 3339, so the original spelling is not in the output. jc keeps more of them as printed.
  • A legend read as data. systemctl list-units closes with a legend explaining LOAD, ACTIVE and SUB; jc returns those lines as units, jz names them in input.ignore and returns the one unit the listing had.
  • Several replies. dig a.com A b.com MX is two replies; jz reads both under one banner, jc reads both, and a text made of two whole dig runs pasted together is refused by jz because a run has one banner.

Formats jc reads that jz does not, and which stay out of scope for now: shell and string values (jwt, url, semver, path, timestamp, email_address), structured file formats that already have readers everywhere (xml, toml, plist, x509_*; jz reads yaml, json, csv and jsonl files as data, by extension), and a set of commands with no definition here yet (traceroute, iptables, iwconfig, dmidecode, mdadm, ntpq, find, finger, tune2fs, ufw, zpool status, zpool iostat, net user, net localgroup, dir). The string and file formats are a different job from reading what a command printed: a JWT or a URL is not a command’s output, and an XML or TOML reader is the tool for a file that already has a grammar. Adding them would make the count of formats larger without making the thing jz is for any better, which is why they are listed here rather than written.

Use cases from jc’s articles #

jc’s author has published articles showing jc used on real work. The ones whose input is a command’s output are read by jz as well:

article’s pipelinewith jz
rpm -qia, then the licence, build date and description with jqrpm -qia | jz (rpm/info)
git log --format=fuller --stat, then files changed, insertions and deletions with jqgit log --format=fuller --stat | jz (git/log-stat)
ifconfig in a script meant to run on Linux and macOS, then the interface name, IPv4 address and netmaskifconfig | jz (ifconfig/net-tools, ifconfig/bsd, ifconfig/busybox)
an ASCII table drawn with +, - and |jz --parser table --variant box
ping -c1, arp -a and wc while scanning a subnetping/linux, ping/bsd, the arp variants, jz --parser wc

The keys are jz’s own and are not meant to match jc’s. The netmask is kept as ifconfig printed it: 255.255.255.0 from net-tools and 0xffffff00 from macOS and FreeBSD.

scripts/compare_jc.py rpm git ifconfig arp table ping wc runs both over the fixtures of these commands. Both read all 63 that jz reads. Of the 17 written to be refused, jz refused 16 (the one it read is the wc -lwcL case above) and jc read 16. The differences in what was read:

  • rpm 6 prints the signature on the line under Signature :. jc reports the signature as null there; jz keeps it.
  • openSUSE’s rpm prints Distribution: after the description, and jc reads that line as part of the description. So does the package NAME is not installed line rpm -qi prints for an unknown name, and a description line that repeats the Name : label becomes a second package. jz reads the first as its own key and refuses the other two.
  • On macOS and FreeBSD jc writes a netmask in dotted form, which is not what ifconfig printed, and leaves out the far end of a point-to-point address (inet 10.0.0.1 --> 10.0.0.2).
  • Under git log --stat --summary, jc lists the mode change line as a changed file. jz refuses the output.
  • In an ASCII table whose value holds a bar (a|b), jc returns a b. jz refuses the row.

Other things the articles do are left out on purpose, because they are not reading what a command printed:

  • Working out a network from an address (192.168.1.10/25 to its network, broadcast, host range, integer and hex forms, and whether it is private). That is a calculator.
  • Decoding a certificate, a CSR or a CRL, and taking apart a URL, a path, a timestamp, an email address, a version string or a JWT.
  • Adding values date did not print, such as the epoch, the day of the year or a 12-hour clock.
  • Reading typeset -p and declare -p. Their quoting is the shell’s own grammar, and the only complete reader of it is the shell.
  • Plugins for Ansible, Salt or Nornir. jz writes JSON to standard output, which any of them can read.
  • Selecting, sorting, grouping or reshaping the JSON. That is jq’s job.

jz reads around ninety commands jc has no parser for, among them the systemd-* tools, most of util-linux (lsfd, lsns, lsmem, lslocks, lsipc, lsirq, lsclocks, uuidparse, namei, prlimit), the lsb_release, loginctl, localectl, hostnamectl and resolvectl family, package and language tooling (apt-cache, npm, cargo, go, rustup, uv, mise, snap, docker), journalctl, sar, sensors, smartctl, pactl, nstat, tc, bridge and the table/csv/kv/ini shape readers.