pkg-remove(1)
NAME
pkg-remove - remove installed packages, or drop a set root
SYNOPSIS
pkg remove [--dry-run] [--cascade] [--force] name [name...]
pkg delete | pkg uninstall | pkg erase
DESCRIPTION
Something is installed and you don't want it anymore. Each argument is either a package name (optionally name/subpkg) or a set reference +set, and you can mix both on one command line.
The two kinds behave differently. That's deliberate, and it's worth knowing why.
Removing a package
A package name expands to its installed subpackages, and those get removed.
Then pkg cleans up the selection roots the removal left pointing at nothing installed. A selection root is something you asked for by name; see pkg-install(1). That includes the root for the name you typed, a coordinate root like vim/enhanced whose subpackage just went, and the roots of dependents that --cascade took along. A bare root stays as long as any of its subpackages is still installed, and pkg-upgrade(1) reinstalls whatever that root still wants.
What it doesn't do is sweep up the dependencies the removal orphaned. Pruning those is a separate decision, and you make it with pkg-autoremove(1).
Dropping a set
A set reference drops the set root and undoes what installing the set did. Everything the set's members reach among the installed packages is a candidate. A candidate stays if another selection root still reaches it, or if any installed package outside the set still needs it. The rest goes, on the same command.
That's the whole point of a set root. Its members are kept alive by the root, not by you having named each of them. Take the root away, and what only it was holding goes too.
Nothing the set never reached is touched. A package that no root explains, left behind by an earlier removal or laid down by the image you installed from, stays put; pruning those is pkg-autoremove(1)'s job. If the chain no longer defines the set, its membership is unknown, so the root is dropped and nothing is removed.
A set never takes a repository qualifier: +dev-tools@Common is a grammar error.
What gets said on stderr
Dropped package roots are listed as "dropped selection root(s): ...", or "would drop selection root(s): ..." under --dry-run.
Naming a package root that isn't installed drops the root instead of refusing. That's how you clear a root that was left dangling, which a bare upgrade would otherwise reinstall. A name that's neither installed nor a root is still an error.
Removing a package that was never a selection root is fine. Dropping a set that was never a root prints "+name was not a selection root" and carries on.
SAFETY
Why was my removal refused?
Before anything is removed, pkg runs a pre-flight GC scan. It refuses the removal when an installed binary still needs a soname the outgoing package provides, when a running process maps one of its files, or when a kernel module from it is loaded. The refusal names each blocker:
error: install: removal blocked by 1 gc check(s); re-run with --force to override: libfoo/lib: /usr/bin/bar still requires 'libfoo.so.1'
There are two ways past it. --cascade removes the dependents too, and lists the full set in the plan first. Or --force skips the scan and reports what it skipped. Pick --cascade when you want the dependents gone as well, and --force when you know better than the scan.
OPTIONS
--dry-run- Compute and print the plan, including what a set drop would remove, and change nothing. The root drop is simulated in memory, so the preview is accurate without writing anything. A dry run never refuses: whatever would block the real removal is listed under
blocked(andblocked_byin JSON). Run as a regular user, the running-process scan can't read other users' processes, so it reports itself unavailable there. --cascade- Also remove every installed package that depends on the named ones (their reverse-dependency closure), instead of refusing when a dependent still needs something being removed. The full set is listed in the plan before anything is applied.
--force- I know, do it anyway. Proceeds past a GC blocker on a removal: a running process maps an outgoing file, an installed binary still needs a soname, a kernel module is loaded. Everything it bypasses is reported, never silenced, and passing it makes the command ask for confirmation.
All global options apply; see pkg(1).
CONFIRMATION
remove is an explicit deletion, so it asks at the default confirm level, destructive. Pass --yes, or set a looser level in pkg.toml(5).
JSON
The plan and the completion line use the same shapes as pkg-install(1): kind: plan, then kind: tx-result.
EXAMPLES
Removing a package
pkg remove htop pkg remove htop --dry-run
Removing one subpackage
pkg remove vim/man
Removing something with dependents
pkg remove libfoo
That's refused, naming what still needs it. Then either:
pkg remove libfoo --cascade --dry-run pkg remove libfoo --cascade
or, if you know better than the scan:
pkg remove libfoo --force
Dropping a set
pkg remove +xfce --dry-run pkg remove +xfce
The dry run shows both halves: the root that would be dropped, and the packages that would go with it.
Keeping one member of a set you're dropping
pkg install thunar # promote it to an explicit root pkg remove +xfce # the rest goes; thunar stays
Removing a package and pruning what it orphaned
pkg remove build-toolchain pkg autoremove --dry-run pkg autoremove
Mixed arguments
pkg remove +monitoring htop
The package is removed directly. The set root is dropped and what it pulled in goes.
Non-interactive
pkg --yes remove htop pkg --json --yes remove htop | jq .removed_count
What would a cascade take?
pkg -vvv remove libfoo --cascade --dry-run 2>&1 | grep 'closure'
EXIT STATUS
0 on success. 1 on any failure, including a refused removal and a declined confirmation.
SEE ALSO
pkg(1), pkg-install(1), pkg-autoremove(1), pkg-upgrade(1), pkg-list-sets(1), pkg.toml(5), pkg-state(5), pkg-sets(7), pkg-subpkgs(7)