Part of a series: Install · Configure · DHCP · Commission · Deploy · jq · SSH · More jq
Previously I showed how to use Linux utilities like awk and sort to sort jq output. There’s also a native jq approach — and understanding why it works the way it does teaches you something important about jq itself.
Starting point
A casually-defined set of machines, not even commissioned:
maas admin machines read | jq -r '(["HOSTNAME","SYSID",
"TAGS", "POOL","ZONE"] | (., map(length*"-"))),
(.[] | [.hostname, .system_id, .tag_names[0] // "-",
.pool.name,.zone.name]) | @tsv' | column -t
Output (unsorted):
HOSTNAME SYSID TAGS POOL ZONE
-------- ----- ---- ---- ----
nearby-louse ed8pda plover swimming defense
novel-cod 4ds3eh dunnet swimming fault
nearby-koala k6cagg xyzzy car twilight
superb-koi 6sqads xyzzy swimming twilight
amazed-hermit mehgsk xyzzy swimming twilight
daring-minnow ceamb4 plover car demilitarized
ace-colt hfm84c zork tidal defense
unique-donkey tsf8tk plover tidal demilitarized
mutual-cod sxbkyp bedquilt tidal defense
fun-ibex 8pq6qx zork car fault
jq is not Unix
The intuitive attempt at sorting:
maas admin machines read | jq -r '(["HOSTNAME","SYSID",
"TAGS", "POOL","ZONE"] | (., map(length*"-"))),
(.[] | [.hostname, .system_id, .tag_names[0] // "-",
.pool.name,.zone.name] | sort_by(.hostname)) | @tsv' | column -t
Error:
jq: error (at <stdin>:2911): Cannot index string with string "hostname"
The problem: jq is not Unix. You can’t just chain pipes and expect filters to work on whatever comes before them the way Unix commands do.
Where does the data come from?
The key question to ask when building jq expressions is: where is the data coming from?
Break the jq block down:
(["HOSTNAME","SYSID","TAG","POOL","ZONE"] | ← literals, no data
(.,map(length*"-"))), ← acts on data, doesn't fetch it
(.[] | ← HERE. this fetches data.
[.hostname, .system_id, ...])
.[] is the only place data enters the expression. That’s the only place sorting can happen.
How sort_by() actually works
sort_by() operates on whatever it receives. To sort the machines, it needs to receive the entire array of machine datasets — before the field extraction happens.
Wrong (sorting after extraction, one record at a time):
(.[] | [...fields...] | sort_by(.hostname))
Right (sorting the array, then extracting fields):
(sort_by(.hostname)[] | [...fields...])
The corrected command:
maas admin machines read | jq -r '(["HOSTNAME","SYSID","TAG","POOL","ZONE"] | (., map(length*"-"))),(sort_by(.hostname)[] | [.hostname,.system_id,.tag_names[0],.pool.name,.zone.name]) | @tsv' | column -t
Output:
HOSTNAME SYSID TAG POOL ZONE
-------- ----- --- ---- ----
ace-colt hfm84c zork tidal defense
amazed-hermit mehgsk xyzzy swimming twilight
daring-minnow ceamb4 plover car demilitarized
fun-ibex 8pq6qx zork car fault
mutual-cod sxbkyp bedquilt tidal defense
nearby-koala k6cagg xyzzy car twilight
nearby-louse ed8pda plover swimming defense
novel-cod 4ds3eh dunnet swimming fault
superb-koi 6sqads xyzzy swimming twilight
unique-donkey tsf8tk plover tidal demilitarized
Read the command left to right: “Take all machine data from MAAS, display a heading and rule; then sort the machine data by hostname and show these 5 parameters in tab-separated, aligned columns.”
Sorting by other fields
By system ID:
sort_by(.system_id)[]
By pool name (nested key — no problem):
sort_by(.pool.name)[]
Sorting by multiple parameters
Pass a comma-separated list. Sort by tag first, then pool:
sort_by(.tag_names[0],.pool.name)[]
Output:
HOSTNAME SYSID TAG POOL ZONE
-------- ----- --- ---- ----
mutual-cod sxbkyp bedquilt tidal defense
novel-cod 4ds3eh dunnet swimming fault
daring-minnow ceamb4 plover car demilitarized
nearby-louse ed8pda plover swimming defense
unique-donkey tsf8tk plover tidal demilitarized
nearby-koala k6cagg xyzzy car twilight
superb-koi 6sqads xyzzy swimming twilight
amazed-hermit mehgsk xyzzy swimming twilight
fun-ibex 8pq6qx zork car fault
ace-colt hfm84c zork tidal defense
Three parameters — tag, pool, then hostname — resolves ties between machines that share both tag and pool:
sort_by(.tag_names[0],.pool.name,.hostname)[]
Note that amazed-hermit and superb-koi swap positions — they share tag xyzzy and pool swimming, so hostname becomes the tiebreaker.
Summary
jq’s sort_by() is cleaner than the awk approach for many use cases. The key insight: make sort_by() the data source, not a filter applied after field extraction. Once that clicks, multi-parameter sorting is straightforward.
There’s still a lot more jq to explore — but this covers the patterns you’ll use 95% of the time with MAAS.