Combining Selections
Scope: how lsof adds selections up —
the OR default, -a for AND, lists and ^
exclusions inside one selection, -c by
command name — with the misleading cases
captured, including one this guide's own
lab walked into.
The rule: selections add up
Every lsof selection you have used so far is
a filter: a name, -p, -i, -u, -c,
-d. What the earlier chapters politely
didn't stress: when you give several
selections, a row needs to match only
one of them. Selections are ORed. The
lab demonstrates with two plants that share
nothing — holder A (pid 22452) holds a file
and no sockets; listener B (pid 22453) holds
port 8734 and no lab files:
[planted: holder A pid 22452 (a file, no sockets); listener B pid 22453 (port 8734, no lab files); tail pid 22454]
$ lsof -p 22452 -i :8734
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 22452 root cwd DIR 0,86 72 7483797 /home/hatch/workspace/lsof-lab
...
python3 22452 root 3r REG 0,86 6 7484186 /home/hatch/workspace/lsof-lab/tree/logic.txt
python3 22453 root 3u IPv4 859175 0t0 TCP localhost:8734 (LISTEN)
python3 22453 root 4u IPv4 859176 0t0 UDP localhost:8734
[exit 0]
Asked for "pid 22452, port 8734," lsof delivered everything pid 22452 holds (15 rows) plus everything on port 8734 (B's 2 rows) — because that's what OR means. If what you meant was "what does 22452 hold on port 8734," the answer is a different command.
-a: make it AND
-a switches the combination to AND — a row
must satisfy every selection:
$ lsof -a -p 22452 -i :8734
[exit 1]
$ lsof -a -p 22453 -i :8734
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 22453 root 3u IPv4 859175 0t0 TCP localhost:8734 (LISTEN)
python3 22453 root 4u IPv4 859176 0t0 UDP localhost:8734
[exit 0]
The empty first answer is the correct
answer: A holds nothing on that port, and
the exit 1 says so. This is why chapter 2's
-d slices were written lsof -a -p … -d …
— the -a there was not ceremony.
The trap this lab walked into
-d is a selection like any other, so it
ORs too. An early lab run asked for "the
cwds of pids A and B" as:
$ lsof -p 22452,22453 -d cwd
and got a screenful: every process's cwd
row on the machine (that selection, alone,
matches them all) plus the complete tables
of both pids (the other selection, alone,
matches those) — systemd's cwd, the lab
shell's, a row of unknown entries for
processes in another corner of the build
sandbox, the lot. The capture is kept whole
in the lab transcripts (session s07) as the
exhibit. The fix is the same -a fence:
$ lsof -a -p 24265,24266 -d cwd
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 24265 root cwd DIR 0,86 72 7483797 /home/hatch/workspace/lsof-lab
python3 24266 root cwd DIR 0,86 72 7483797 /home/hatch/workspace/lsof-lab
[exit 0]
Exactly two rows. (New plants, new pids —
the rerun is its own session, s07b.) When an
lsof command prints far more than the
question asked, the missing -a is the
first suspect, every time.
Inside one selection: lists and ^
Within a single selection, commas make a
list (also an OR — any item may match), and
^ at the front of an item excludes it:
$ lsof -a -p 24265,^24266 -d cwd
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 24265 root cwd DIR 0,86 72 7483797 /home/hatch/workspace/lsof-lab
[exit 0]
"A and B" gave two rows; "A and not-B" gave
one. The same list grammar works in -u
(users) and -c (commands), and the
exclusion form is how you say "this port,
but not that process" without a pipeline.
By command name: -c
-c selects by the COMMAND column — exact
name, or a /regex/ between slashes:
$ lsof -c tail
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tail 22454 root cwd DIR 0,86 72 7483797 /home/hatch/workspace/lsof-lab
...
tail 22454 root 3r REG 0,86 7 7484187 /home/hatch/workspace/lsof-lab/tree/logic.log
tail 22454 root 4r a_inode 0,17 0 59 inotify
[exit 0]
The planted tail's whole table from its
name alone. Two cautions ride along: the
COMMAND column is the executable's name
truncated to 9 characters (systemd-j,
redis-ser in earlier captures), so -c
matches that truncated name; and a script
runs under its interpreter's name — the
lab's hold.py processes are python3 to
lsof, which is why the chapters select them
by pid. -c shines for daemons with real
names of their own.
Summary of the logic, worth memorising:
different selection types add (OR);
-a intersects (AND); inside one selection,
commas add and ^ subtracts. Most
"lsof printed the wrong thing" stories are
the first clause.