pkg-list(1)
NAME
pkg-list - list installed packages, or everything the chain makes current
SYNOPSIS
pkg list [--all] [--collapse=LEVEL]
DESCRIPTION
Ask a typical Linux machine what's installed and you get a list of 500+ package names. Nobody can read that. Nobody remembers why half of them are there, and the honest answer is that they never chose them: they installed a desktop and a few tools, and the rest got dragged in.
So pkg list doesn't start from what the build produced. It starts from what you chose. With no options, it lists the installed set collapsed as far as it goes: every row one of your selection roots reaches becomes part of that root's row, and whatever is left over gets one row per package name, with its version-release and the repository it came from.
A selection root is something you asked for: a set like +xfce, or a package like htop that you installed by name. pkg-state(5) records them.
What does "reaches" mean?
A root reaches a row when it names it, which is declared set membership, or when its dependency closure pulls it in. The second half is what makes the default useful. libX11 is on a desktop machine because xorg-server links it, and no curated set will ever name it. Collapse on declared membership alone and libX11 stays in the leftovers, looking unaccounted-for next to a hundred other libraries in the same position. That stricter question is still there, as --collapse=sets, because sometimes it's the one you're asking.
Why roots and not subpackages?
A bare name is something someone typed. The split into bin, lib and data is something autobuilder-classify decided. A set root is the same thing again, one level up. So the default shows the level you chose, and --collapse walks down from there. Nothing goes missing: every installed package name still shows up on a row, unless a root accounts for it.
And --all?
With --all you get every package the configured repositories make current instead. That's the chain's answer, after position-decides and name-level shadowing have been applied; see pkg-chain(7).
OPTIONS
--all- List every package the chain makes current, not just the installed set.
--collapse=LEVELHow far the human listing collapses. This is a global option (pkg(1)), so the same flag folds a transaction plan, a compose closure and a search result. The two top levels, though, are questions only this command can answer, and everywhere else they're refused. The levels are ordered, and each one includes the one below it:
roots: Every row a selection root reaches, set or package, by declared membership or through the dependency closure, folds into that root's row. Everything else collapses the waymainwould. This is what you chose, and it's the default for the installed listing. Refused under--all; see below.sets: Likeroots, but a set only covers what it literally names. The libraries it pulled in stay on their own rows. Package roots still get their own row. On a machine installed from+sys, this tells you how much of the list the set itself declares.main: Each package's runtime subpackages fold into one row, with the classes it carries in brackets. Non-runtime subpackages (dev,man,doc,lang,server,debug,debugsrc,source) stay on their own name/subpkg row. The default under--all.none: One row per subpackage. Nothing collapses, and there's no set overlay.
This only changes the human output. JSON is always the flat per-subpackage list, at every level.
All global options apply; see pkg(1).
COLLAPSING
Why does --all default to main?
Your selection roots explain your machine. They don't explain the chain's catalogue, and folding the catalogue by them would make --all depend on a state read it otherwise doesn't need. Asking for --collapse=roots under --all is refused outright, not quietly turned into something else, because there's no honest answer to it over a catalogue nothing was installed from:
error: --collapse=roots explains the installed set from this machine's selection roots; --all lists the chain's catalogue. Use --collapse=sets or --collapse=main.
What does a set member cover?
A bare name in a set covers that package's runtime subpackages. That's the same expansion pkg-install(1) used to put them there, including whatever [install] include_subpkgs adds. So a dev, man or debug subpackage the set never asked for stays on its own row. A name/subpkg member covers exactly that subpackage.
What if two sets name the same package?
Then it's credited to both, and leaves the listing once. The bracket answers "how much of this set is here", not "how many lines did this root remove". That means the fractions don't add up to the rows the collapse hid, and that's on purpose. Crediting each row to just one root, whichever sorted first, made a set that fully explains its 30 installed members report 0 packages. That's false in exactly the way that matters.
What about a set that's gone?
Every recorded set root gets a row, even when it accounts for nothing. That includes one the chain no longer defines: it's reported, not dropped. The version column reads -, because a set has no version. It names members, and the pins chain decides what each of those currently is. The repository column lists every repository contributing members, since sets are unioned across the chain instead of shadowed (see pkg-sets(7)).
OUTPUT
Here's the default, on a machine built from three sets plus one package someone asked for by name:
+lightdm-login - Common [40/40 members, +12 dependencies] +xfce - Common [66/66 members, +204 dependencies] +xorg - Common [30/30 members, +58 dependencies] htop 3.4.1-2 Common [/bin] [root, +3 dependencies] alsa-lib 1.2.16.1-2 Common [/bin, /data, /lib] alsa-lib/dev 1.2.16.1-2 Common
- Four roots, and the leftovers
The fraction on a +set row is how much of that set is present, out of its flattened membership. It's the same fraction pkg-list-sets(1) prints, so the two commands can't disagree about the same set. It counts members, not rows, so it doesn't move with --collapse.
The +N dependencies part is the other half: what that root's closure pulled in next to its members. That's what roots folded away and sets would have left on their own rows. You don't get it under --collapse=sets, which never walks the closure.
A package root keeps its own row, marked [root]. It has no row except its own, so hiding it would delete the intent instead of summarising it. One that isn't installed is still reported:
htop - - (not installed)
A set that explains nothing says why:
+ghost - - (not defined by any repo in the current chain)
--collapse=sets asks the stricter question. Same rows, but only credited where a set actually names the member:
+lightdm-login - Common [40/40 members] +xfce - Common [66/66 members] +xorg - Common [30/30 members] htop 3.4.1-2 Common [/bin] [root] libX11 1.8.12-3 Common [/lib] libXau 1.0.12-2 Common [/lib] ...
--collapse=main drops the overlay and lists every name:
alsa-lib 1.2.16.1-2 Common [/bin, /data, /lib] alsa-lib/dev 1.2.16.1-2 Common
--collapse=none is one row per subpackage:
alsa-lib/bin 1.2.16.1-2 Common alsa-lib/data 1.2.16.1-2 Common alsa-lib/dev 1.2.16.1-2 Common alsa-lib/lib 1.2.16.1-2 Common
With nothing to show:
(no packages)
The repository column
The third column is the repository in the chain that answers for the name. It reads pins.d for a name you've held with pkg-pin(1), since the pin is what answers for it now. It reads local when you ran with a bare --repo directory instead of configured repositories. And it reads - when no configured repository answers for the name at all, which for an installed package means it's held; see pkg-upgrade(1).
NETWORK
Does it need the network?
No. What's installed is a question about /var/lib/pkg, and you get the answer whether or not any repository can be reached. The repository column and the set overlay are labels on that answer. They're read from signed metadata pkg already verified and cached, and the repositories aren't asked at all. So the labels are as current as the last command that went online: pkg-upgrade(1), pkg-install(1), pkg-check-update(1), pkg list --all, and the like.
This is deliberate, and it's narrow. It applies to reads that decorate output. It never applies to pkg-install(1), pkg-upgrade(1) or anything else that decides what goes onto the system. Those always ask the repositories.
Where does the cache live?
Yours is under $XDG_CACHE_HOME/pkg/metadata (else ~/.cache/pkg/metadata), root's under /var/cache/pkg/metadata, which everyone can read. A listing uses whichever of the two saw the newer repository state, so if you only ever upgrade with sudo, your listing still keeps up.
Only when neither cache has anything for a repository does pkg list ask it, once, and fill your cache. If the repository can't be reached then, the listing still comes out, the column reads -, and stderr says why. So you can tell it apart from a repository that really publishes nothing.
JSON
{ "ok": true, "source": "installed" | "repo", "packages": [ { "name":..., "subpkg":..., "variant":..., "version":..., "release":..., "arch":..., "repo":... } ] }
repo is null when no configured repository answers. variant is null except for multi-installable packages. JSON is always the flat per-subpackage list, at every --collapse level.
Set attribution comes along at every level too. It attributes, it never removes: no row ever leaves the JSON, so whether it's there can't depend on a flag that's about human presentation. Every subpackage keeps its entry and gains the roots that cover it:
{ "ok": true, "source": "installed", "packages": [ { ..., "covered_by": ["xfce", ...], "pulled_by": ["+xfce", "htop", ...] } ], "sets": [ { "name":..., "repos": [...], "members_present":..., "members_total":..., "deps_pulled":..., "unresolved": null } ], "package_roots": [ { "name":..., "installed": true, "deps_pulled":... } ] }
covered_by is every set root whose declared membership names that subpackage, in root order, and empty when none does. That's the --collapse=sets question. pulled_by is every root whose dependency closure reaches it, set or package. Those carry their sigil (+xfce for a set, htop for a package), because set and package names share one namespace and a bare label could mean either. That's the --collapse=roots question.
package_roots has one entry per recorded package root, installed or not. deps_pulled, there and on each set, counts what that root's closure brought in beside its own row or members. members_present counts the set's members in the listing (installed, or offered by the chain under --all) out of members_total, its flattened membership. A member two sets both name is counted by both, so these don't add up to the rows a collapsed listing hides. unresolved is a sentence when the set doesn't expand, else null.
The closure keys (pulled_by, deps_pulled) are filled in whenever the graph was walked. JSON always walks it; the human listing only does at --collapse=roots.
The set keys are missing only when the chain's set metadata couldn't be read at all, and then there's also a warning on stderr. A listing has to list: an unreachable chain costs you the overlay, never the packages. pkg list-sets --all is the command that answers for the chain's sets authoritatively, and that one fails loudly.
EXAMPLES
What's installed?
pkg list pkg list --collapse=main
What's on offer?
pkg list --all pkg list --all --collapse=none
What don't my roots explain?
pkg list
The rows that aren't a root are the answer. pkg list-sets asks the opposite question: how much of each set is installed, instead of how much of the installed list each root accounts for.
Everything installed that no root pulled in
pkg --json list \ | jq -r '.packages[] | select(.pulled_by == []) | "(.name)/(.subpkg)"'
Everything no set literally names
The stricter question, which is what covered_by answers:
pkg --json list \ | jq -r '.packages[] | select(.covered_by == []) | "(.name)/(.subpkg)"'
Counting
pkg list | wc -l pkg list --collapse=none | wc -l pkg --json list | jq '.packages | length'
Distinct package names, ignoring subpackages
pkg --json list | jq -r '.packages[].name' | sort -u
Which repository each installed package came from
pkg --json list | jq -r '.packages[] | "(.repo)\t(.name)"' \ | sort | uniq -c | sort -rn
What's installed that the chain no longer offers?
pkg --json list --all | jq -r '.packages[] | "(.name)/(.subpkg)"' \ | sort -u > /tmp/offered pkg --json list | jq -r '.packages[] | "(.name)/(.subpkg)"' \ | sort -u > /tmp/installed comm -23 /tmp/installed /tmp/offered
pkg upgrade --dry-run tells you the same thing as held names, with the reasoning attached.
Which debug symbols are installed?
pkg list --collapse=none | grep '/debug'
Which locales are installed?
pkg list --collapse=none | grep '/lang/' | sed 's|.*/lang/||' | awk '{print $1}' | sort -u
Everything at a given version
pkg --json list | jq -r '.packages[] | select(.version == "1.13")'
Listing a mounted image instead of the live system
pkg --state-root /mnt/image/var/lib/pkg list
Listing what one unconfigured repository carries
pkg --repo /srv/mirror/Common --repos-dir /nonexistent list --all
Diffing two machines
ssh hostA 'pkg --json list' | jq -r '.packages[] | "(.name)/(.subpkg) (.version)-(.release)"' | sort > /tmp/a ssh hostB 'pkg --json list' | jq -r '.packages[] | "(.name)/(.subpkg) (.version)-(.release)"' | sort > /tmp/b diff -u /tmp/a /tmp/b
EXIT STATUS
0 on success, including an empty list. 1 on failure: --collapse=roots under --all, or an installed state that can't be read when listing it.
SEE ALSO
pkg(1), pkg-search(1), pkg-query(1), pkg-info(1), pkg-list-sets(1), pkg-install(1), pkg-pin(1), pkg-state(5), pkg-chain(7), pkg-sets(7), pkg-subpkgs(7)