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 | |
|---|---|
| read | piping the output in identifies it and converts it |
| named | use jz --parser NAME or jz run; some formats also need --variant VARIANT |
| refused | jz 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.
Storage and memory #
| invocation | definition | |
|---|---|---|
df | read | df/gnu |
df -k | read | df/gnu |
df -a, df --total | read | df/gnu |
df -h, df -H | read | df/gnu-human |
df -m, df -B512, df --block-size=N | read | df/gnu-blocks |
df -T | read | df/gnu-type |
df -T -m | read | df/gnu-blocks-type |
df -i | read | df/gnu-inodes |
df -ih | read | df/gnu-inodes-human |
df -P, df -PT | read | df/portable, df/portable-type |
df --output=... | refused | the columns are whatever the option named; jz --parser table --variant aligned reads it as a table |
free | read | free/gnu |
free -h | read | free/gnu-human |
free -w | read | free/gnu-wide |
free -h -w | read | free/gnu-wide-human |
free -t, free -l, free -s N -c N | read | the variant the other options chose |
lsblk | read | lsblk/linux |
lsblk -b | read | lsblk/bytes |
lsblk -f | read | lsblk/filesystems |
lsblk -t | read | lsblk/topology |
lsblk -m | read | lsblk/permissions; in a container without the device nodes, owner, group and mode are null together |
lsblk -P | read | lsblk/pairs |
lsblk -r | read | lsblk/raw |
lsblk -n | named | lsblk/no-headings; with no header the text is a tree drawing and some words |
lsblk -J | refused | it is JSON already |
lsblk -o ... | refused | the columns are whatever the option named; --parser table reads it |
mount, mount -l, mount -t TYPE | read | mount/linux |
/proc/mounts, /etc/mtab | named | jz --parser etc reads them as etc/fstab, which is their format |
stat FILE | read | stat/gnu |
stat FILE FILE | read | stat/gnu, one object per file |
stat -f | read | stat/gnu-filesystem |
stat -t | named | stat/gnu-terse; a row of numbers describes too many things |
stat -c, stat --printf | refused | the 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 #
| invocation | definition | |
|---|---|---|
ps | read | ps/posix |
ps -ef | read | ps/unix |
ps aux | read | ps/bsd |
ps -el | read | ps/long |
ps -eF | read | ps/full-format |
ps -ely | read | ps/long-y |
ps -eLf | read | ps/threads |
ps axjf, ps -e --forest | read | ps/jobs, ps/posix |
ps -eo ..., ps -o ...= | refused | the columns are whatever the option named; --parser table reads it |
top -b -n1 | read | top/linux |
pidstat, pidstat -d, -r, -w | read | the pidstat variants |
Listings #
| invocation | definition | |
|---|---|---|
ls -l | read | ls/long |
ls -la, -lh, -lt, -lS, -ln | read | ls/long |
ls -l --full-time | read | ls/full-time |
ls -l --time-style=long-iso | read | ls/long-iso |
ls -lZ | read | ls/long-context |
ls -lR | read | ls/long-recursive |
ls -log, ls -lgG | read | ls/long-no-owner-group |
ls -lG, ls -lo | named | ls/long-no-group |
ls -lg | named | ls/long-no-owner |
ls -li | named | ls/long-inode; ls -ls prints a number in the same place |
ls, ls -1 | named | ls/names; a list of lines is not evidence of anything |
ls -F, ls -p | refused | the 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 #
| invocation | definition | |
|---|---|---|
ip address, ip -4 address, ip -6 address | read | ip/address |
ip -br address, ip -o address | read | ip/brief-address, ip/oneline-address |
ip link, ip -d link | read | ip/link |
ip -br link | read | ip/brief-link |
ip -s link, ip -s -s link | read | ip/stats-link, ip/stats-link-detail |
ip route, ip -6 route | read | ip/route |
ip neigh, ip rule, ip maddr | read | the matching ip variants |
ip -j ... | refused | it is JSON already |
ip tunnel, ip netns, ip xfrm | refused | no definition; the subcommands are each their own format |
ss, ss -a, ss -l, ss -x, ss -tuln, ss -p | read | ss/linux |
ss -e, ss -o, ss -m, ss -i | read | ss/linux, with what the option added under info |
ss -t, ss -ua, ss -ul | read | ss/single-protocol |
ss -u, ss -w | read | ss/connected |
ss -s | read | ss/summary |
ss -H | refused | -H removes the header, which is the line that says what the format is |
netstat, -a, -t, -u, -x, -w, -l, -n, -e | read | netstat/all-sockets, netstat/internet, netstat/unix |
netstat -r, netstat -rn | read | netstat/routing |
netstat -e -r | read | route/linux, which is the table those columns are |
netstat -i | read | netstat/interface |
netstat -s | refused | which 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 Windows | refused | no definition; no vendor-published text sample was found to write one against, only screenshots |
dig NAME | read | dig/bind |
dig NAME A NAME MX | read | dig/bind, one entry in replies per query |
dig +nocmd | read | dig/bind, with an empty query |
dig +nsid, +dnssec, +tcp, +norec | read | dig/bind |
| a query that reached no server | read | dig/bind, with an empty replies and the attempt under transport |
dig axfr | read | dig/axfr |
dig +noall +answer | named | dig/answers; the same five columns are what a zone file holds |
dig +short | refused | the output is the data of the records with nothing naming them |
dig +trace | refused | the delegation steps have no line that opens one, so nothing says where a step ends |
ping -c N, -q, -D, -A, -s, -W, ping6 | read | ping/linux, ping/bsd |
ping -f | refused | flood output is a line of dots, not records |
tracepath | read | tracepath/linux |
traceroute | refused | no definition |
systemd #
| invocation | definition | |
|---|---|---|
systemctl, systemctl list-units, --all, --plain, --failed | read | systemctl/units |
systemctl list-unit-files | read | systemctl/unit-files |
systemctl list-timers | read | systemctl/timers |
systemctl list-sockets | read | systemctl/sockets |
systemctl list-jobs, including with nothing queued | read | systemctl/jobs |
systemctl list-machines | read | systemctl/machines |
systemctl list-paths | read | systemctl/paths |
systemctl list-automounts | read | systemctl/automounts |
systemctl list-dependencies --plain | read | systemctl/dependencies |
systemctl status UNIT | read | systemctl/status |
systemctl show UNIT | read | systemctl/show |
systemctl show | named | systemctl/show-properties; NAME=value is also what env prints |
systemctl list-dependencies | refused | without --plain a line carries a state marker and a drawing, and a line’s depth then depends on both |
systemctl status | refused | the whole-system report is a different format with no definition |
systemctl list-units --no-legend | refused | the option removes the header, which is what says the format is this one |
systemctl cat UNIT | refused | it is the unit file; jz --parser ini reads that |
journalctl -o short* | read | the journalctl variants |
Repeating reports #
| invocation | definition | |
|---|---|---|
vmstat, vmstat N M, vmstat -w | read | vmstat/linux |
vmstat -a, -d, -s | read | vmstat/linux-active, -disk, -stats |
vmstat -f | refused | one line saying how many forks there have been |
iostat, iostat N M | read | iostat/linux |
iostat -x, -c, -d, -h | read | iostat/extended, cpu, device, human |
iostat -t and each of those with -t | read | the -timestamped variant of each |
mpstat, mpstat -I | read | mpstat/linux, mpstat/interrupts |
sar -u, -r, -b, -n DEV, … | read | the 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.
| invocation | definition | |
|---|---|---|
ipconfig /all | read | ipconfig/all |
ipconfig | read | ipconfig/windows |
systeminfo, systeminfo /fo list | read | systeminfo/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 csv | named | jz --parser csv --variant comma |
ipconfig /displaydns | refused | a different format; pinned as a refusal rather than read |
netstat on Windows | refused | no vendor-published text sample was found to write one against, only screenshots |
net user, net localgroup, dir, route print, ver, tasklist, wmic | refused | no 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, andjz --parser table --variant alignedreads an aligned table under whatever headings it has. - Output with the header removed (
ss -H,systemctl --no-legend,lsblk -nby 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=jsonanywhere). 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 #
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 -hprints13G; jc reports13958643712and 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 to13Gcome 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%into7,TIME 00:02:57into177seconds 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-unitscloses with a legend explaining LOAD, ACTIVE and SUB; jc returns those lines as units, jz names them ininput.ignoreand returns the one unit the listing had. - Several replies.
dig a.com A b.com MXis 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, rpm -qi,
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.
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.