Inspect Linux File Descriptors with /proc/<pid>/fd
You will finish with a repeatable way to see what a Linux process has open, identify pipes and sockets, and pass an existing descriptor to a command without creating a second copy of the underlying file. The examples use the local proc_pid_fd(5) reference from the manpages package, version 6.7-2.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Confirm the local reference and your own descriptors
- 2. Read the target of a descriptor
- 3. Inspect a separate process
- 4. Recognise pipes, sockets and anonymous descriptors
- 5. Use /proc/self/fd as a command-line input or output
- 6. Understand the permission boundary
- 7. Account for a multithreaded process
Allow about fifteen minutes. You need a shell and a process you are allowed to inspect. The first checks are ordinary, read-only commands. Looking at another user's process may require elevated privileges, but sudo does not make a descriptor readable when the process or its underlying inode denies access.
1. Confirm the local reference and your own descriptors
Start with the installed manual page, then list the descriptors belonging to the shell running the command:
$ man 5 proc_pid_fd
$ printf 'shell pid: %s\n' "$$"
shell pid: 24680
$ ls -l /proc/self/fd
total 0
lrwx------ 1 user user 64 Sep 26 12:00 0 -> /dev/pts/3
lrwx------ 1 user user 64 Sep 26 12:00 1 -> /dev/pts/3
lrwx------ 1 user user 64 Sep 26 12:00 2 -> /dev/pts/3
lr-x------ 1 user user 64 Sep 26 12:00 3 -> /proc/24680/fd
The names are descriptor numbers, not ordinary file names. Descriptors 0, 1 and 2 are standard input, standard output and standard error. The final entry is an example: shells and commands can open extra descriptors, and the exact list will vary. The /proc/self component means the process making the lookup, so it is useful inside scripts and avoids hard-coding your current PID.
Checkpoint: ls -l /proc/self/fd should return a list rather than a missing-directory error. If it does not, check that you are on Linux and that /proc is mounted. Do not create a replacement directory: it would not provide process descriptors.
2. Read the target of a descriptor
Use readlink when you need the link target without the visual formatting added by ls:
$ readlink /proc/self/fd/0
/dev/pts/3
$ readlink /proc/self/fd/1
/dev/pts/3
These results describe the terminal attached to this shell. A redirected command has different targets. Make a short-lived shell with an extra descriptor and inspect it from inside that process:
$ bash -c 'exec 3<<<"descriptor demo"; printf "fd 3: "; readlink /proc/self/fd/3; IFS= read -r line <&3; printf "read: %s\n" "$line"'
fd 3: pipe:[123456789]
read: descriptor demo
The inode number in the pipe target is illustrative and changes on every run. The descriptor is a live pipe endpoint, not a regular file. The shell can read from it through descriptor 3 because it already owns the open descriptor.
3. Inspect a separate process
To inspect a process that remains alive long enough to examine, start a harmless sleep child and save its PID:
$ sleep 60 &
[1] 24701
$ pid=$!
$ ls -l "/proc/$pid/fd"
total 0
lrwx------ 1 user user 64 Sep 26 12:01 0 -> /dev/pts/3
lrwx------ 1 user user 64 Sep 26 12:01 1 -> /dev/pts/3
lrwx------ 1 user user 64 Sep 26 12:01 2 -> /dev/pts/3
$ readlink "/proc/$pid/fd/0"
/dev/pts/3
List the directory before reading individual entries because the process can exit between those two operations. Treat a disappearing /proc/$pid path as a normal race, not as proof that the descriptor never existed.
Checkpoint: confirm that the process is still the one you started, then clean it up:
$ kill "$pid"
$ wait "$pid" 2>/dev/null || true
[1]+ Terminated sleep 60
This is the only state-changing example in the guide, and it affects only the temporary child. Never substitute a production PID into kill without checking it first. If you terminate a service by mistake, use that service's normal recovery procedure rather than repeatedly sending signals.
4. Recognise pipes, sockets and anonymous descriptors
Not every target names a path. The manual describes pipe and socket links in the form type:[inode]. An entry such as socket:[2248868] identifies a socket inode; the number can be correlated with files under /proc/net when you are diagnosing a network process.
Kernel objects without a normal inode use an anon_inode: target. Examples include epoll, eventfd, inotify, timerfd and signalfd descriptors:
$ readlink "/proc/$TARGET_PID/fd/$FD_NUMBER"
anon_inode:[eventpoll]
Replace both placeholders after checking the directory listing. The descriptor number and object type are host-specific. Do not infer that every anonymous descriptor is an epoll instance, and do not treat a link target as permission to manipulate the object.
5. Use /proc/self/fd as a command-line input or output
A program can often be given an existing standard stream through /proc/self/fd/N. This helps when a tool insists on a file argument but your data is already on standard input or output. Use a command that treats its argument as an input file:
$ printf 'one\ntwo\n' | cat /proc/self/fd/0
one
two
Here, cat opens its own /proc/self/fd/0, which refers to the pipe it inherited. This is approximately the same idea as /dev/fd/0 on systems that provide that name. It is not universal application behaviour: a program that seeks may fail on a pipe, and a tool may treat a descriptor path differently from a regular file.
6. Understand the permission boundary
Reading a link under another process's fd directory is subject to the kernel's ptrace access check. Opening the object named by that link has another permission boundary: the underlying inode keeps its own owner and mode. These checks explain a common surprise: a process can read from its already-open standard input while a different, unprivileged process cannot open /proc/<pid>/fd/0 to obtain a new reference to the same pipe.
When an inspection fails, record which operation failed. "Cannot read the link" points at process inspection permissions. "Permission denied" while a command opens the resolved path may point at the pipe, socket or file itself. Try the same check against a process you own before using elevated privileges. If the descriptor belongs to a service, obtain approval before reading potentially sensitive data or exposing its target in logs.
7. Account for a multithreaded process
On a multithreaded process, the fd directory may no longer be available after the main thread has terminated, commonly after pthread_exit. If a long-running process appears alive but its descriptor directory is unavailable, check its thread and process state before assuming that /proc is broken. A supervisor restart can change every PID and descriptor, so capture evidence before restarting a service.
Done means
- You can list your own descriptors with
/proc/self/fdand explain entries 0, 1 and 2. - You can use
readlinkto distinguish paths, pipes, sockets andanon_inodeobjects. - You tested a temporary child and removed it without touching a service.
- You know that reading a link and opening its target can fail for different permission reasons.
- You will check for seek requirements before handing a
/proc/self/fdpath to another program.