Skip to content

What lsof Is

Scope: the idea — a process's open files as one table you can search — plus your version, the size of the full answer, and the first lab plant: a tail found from nothing but the file it holds.

Everything is a file, and it's a list

Unix-like systems hand a process almost everything through the same door: the file descriptor, a numbered handle. A document gets one. So does the terminal the process is attached to, the pipe feeding it, the directory it's standing in, and each network connection — a socket is reached through a file descriptor too, even though no file on any disk corresponds to it. "Open files," in lsof's sense, means everything a process currently holds a handle to.

lsof — the name is the command, "list open files" — walks every process and prints one row per handle. That single table answers the three questions this guide keeps asking:

  • Who has this? A file, a directory, a port — name it, and lsof names the process.
  • What is this process holding? Give a pid and get its whole open world.
  • Where did the space go? A file can be deleted from every directory and still exist — as long as a process holds it open. Chapter 4.

The version on the build machine, and the package behind it:

$ lsof -v 2>&1 | head -4
lsof version information:
    revision: 4.95.0
    latest revision: https://github.com/lsof-org/lsof
    latest FAQ: https://github.com/lsof-org/lsof/blob/master/00FAQ
$ dpkg -l lsof | tail -1
ii  lsof           4.95.0-1build3 amd64        utility to list open files

Run with no arguments, lsof prints every open file of every process it can see. On the build machine that answer is not short:

$ lsof | wc -l
3934

Nearly four thousand rows on an idle-ish machine — which is why nobody reads lsof bare. The whole craft is in the selection: naming the file, the process, the user, or the port you care about and letting everything else fall away. The selections are the next ten chapters; this one plants something to select.

The first plant

The lab's simplest holder is a tail -f following a small log file — the classic "a process is watching this file" specimen:

$ tail -f tree/notes.log &   [started pid 22329]

Now ask the first question. Give lsof the file's name, and it answers with the holder:

$ lsof /home/hatch/workspace/lsof-lab/tree/notes.log
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
tail    22329 root    3r   REG   0,86       23 7484136 /home/hatch/workspace/lsof-lab/tree/notes.log
[exit 0]

One row. Read it left to right, because every row in the guide has this shape:

Column This row Meaning
COMMAND tail the program's name (truncated to 9 characters)
PID 22329 the process ID — the lab planted it, so this is checkable
USER root whose process it is
FD 3r file descriptor 3, opened for reading — chapter 2 decodes this column fully
TYPE REG a regular file (directories are DIR, sockets IPv4/IPv6, and more)
DEVICE 0,86 major,minor numbers of the filesystem holding it
SIZE/OFF 23 for a regular file, its size in bytes — the log really is 23 bytes
NODE 7484136 the file's inode number
NAME the path what the handle points at

Check: the pid in the row is the pid the shell reported when the plant started, the size matches the two short lines written into the log, and no other process appears — nothing else on the machine had that file open. That is the basic move of the whole guide: plant a known state, ask lsof, and grade its answer against what you planted.

Two habits to start now. First, lsof prints the path in NAME exactly as the kernel knows it — usually absolute, as here, even though the lab asked with an absolute path too; chapter 3 looks at what name arguments actually match. Second, lsof's silence is information with an exit code attached: ask about a file nobody holds and you get no rows and exit 1 (chapter 7 captures it). An empty answer is an answer.

Next: the same tail, asked by pid instead of by file — its whole table, and the FD column taken apart letter by letter.