fuser
Identify processes using a file, directory or socket
fuser names the processes using a file, a directory or a socket. It answers a narrower question
than lsof, and it comes with -k, which signals everything it found without a
process id ever being typed.
Its output is split across two streams, which can catch people out. The process ids go to standard
output; the filename, the access letters and everything -v adds go to standard error. Redirecting
one leaves you holding the other, which is why every example here sends one somewhere explicit.
-m widens a path to the whole filesystem mounted there, which is the form to run when umount
says a device is busy. -s prints nothing at all and puts the answer in its exit status.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
the files under /srv the setup script starts six small Python services, and every process named on this page is one of them. tips-log appends to app.log, tips-job runs as user and reads both queue.dat and spool/job-0142.json, tips-api listens on port 8080, tips-client holds one connection to it, tips-archiver writes site.tar, and tips-tmp deleted a scratch file it still has open. /srv/cache is a tmpfs of its own, so -m reports on that rather than on the container's root. The same set backs lsof.
/srv/cache:
site.tar
/srv/tips:
app.log
queue.dat
spool
/srv/tips/spool:
job-0142.json
What has this open
-v turns the compact answer into a table with a row per process. The ACCESS column is five positions, one per kind of use: f for an open file, c for a working directory, r for a root directory, e for a running executable and m for a mapping. A capital F or R means the same as the lower-case letter with write access.
Find out what is writing to a log file
fuser -v /srv/tips/app.log 2>&1 >/dev/null
The capital F says the file is open for writing. The redirect keeps the process id out of the way so the table arrives on its own.
Show output
USER PID ACCESS COMMAND
/srv/tips/app.log: root F.... tips-log
Get the process ids on their own
fuser /srv/tips/app.log 2>/dev/null
Everything but the ids went to standard error, so discarding that stream leaves a list kill or xargs can take. The leading spaces are padding, and there is no newline at the end of it.
Show output
Your output will differ: the pid is this machine's
43
See both streams the way a terminal does
script -qec 'fuser -v /srv/tips/app.log' /dev/null
With a terminal attached the two streams interleave into one aligned table, PID column included, which is why the split goes unnoticed until something is piped. script attaches one here.
Show output
Your output will differ: the pid is this machine's
USER PID ACCESS COMMAND
/srv/tips/app.log: root 43 F.... tips-log
Ask about several files at once
fuser -v /srv/tips/app.log /srv/tips/queue.dat 2>&1 >/dev/null
A row per file, and the access letters tell the two apart: F for the process appending to the log, lower-case f for the one only reading the queue.
Show output
USER PID ACCESS COMMAND
/srv/tips/app.log: root F.... tips-log
/srv/tips/queue.dat: user f.... tips-job
Show the owner of each process
fuser -uv /srv/tips/queue.dat 2>&1 >/dev/null
-u appends the owner in brackets to the command name. The USER column already carries it under -v, so -u is for the compact form, which has no such column.
Show output
USER PID ACCESS COMMAND
/srv/tips/queue.dat: user f.... (user)tips-job
Read the answer for a long filename
fuser -v /srv/tips/spool/job-0142.json 2>&1 >/dev/null
A name too wide for its column pushes the rest of the row onto the next line. Worth knowing before writing anything that reads this output by column position.
Show output
USER PID ACCESS COMMAND
/srv/tips/spool/job-0142.json:
user f.... tips-job
Find what a directory is being used as
fuser -v /srv/tips 2>&1 >/dev/null
The c in the third position says how each of these four is using the directory: as a working directory rather than as an open file. Nothing here would stop you deleting a file inside it, which is the distinction -m makes.
Show output
USER PID ACCESS COMMAND
/srv/tips: root ..c.. tips-log
root ..c.. tips-api
user ..c.. tips-job
root ..c.. tips-client
The exit status, and using it
fuser exits 0 when it found something and 1 when it did not, which makes it usable as a test without reading either stream. -s is the same search with both streams silenced.
Test whether a file is in use
for f in /srv/tips/app.log /etc/passwd; do
if fuser -s "$f"; then echo "$f: in use"; else echo "$f: free"; fi
done
-s prints nothing at all, so the if reads only the exit status. This is the form to put in front of a rotate, a truncate or a mv that would otherwise land on a file something is mid-write on.
Show output
/srv/tips/app.log: in use
/etc/passwd: free
See what an empty answer looks like
fuser -v /etc/passwd 2>&1 >/dev/null; echo "exit $?"
Nothing holds it, so there is no table and no header either: fuser prints the heading only when it has a row to put under it.
Show output
exit 1
Keep the files nothing is using in the listing
fuser -av /srv/tips/app.log /etc/passwd 2>&1 >/dev/null; echo "exit $?"
-a gives a row to every name you asked about, whether or not anything holds it. Without it a file with no holders is silently absent, which is hard to tell apart from a name you typed wrong.
Show output
USER PID ACCESS COMMAND
/srv/tips/app.log: root F.... tips-log
/etc/passwd:
exit 0
Ask about a file that has been deleted
fuser -v /srv/cache/scratch.dat 2>&1 >/dev/null; echo "exit $?"
fuser reaches a file through its name, so a file that has been unlinked while a process still holds it open is beyond it entirely. lsof +L1 finds those, and they are the usual reason a filesystem stays full after a deletion.
Show output
Specified filename /srv/cache/scratch.dat does not exist.
exit 1
Count the processes holding a file
fuser /srv/tips/queue.dat 2>/dev/null | wc -w
wc -w rather than wc -l, because the ids arrive space-separated on a single line with no newline after them. The count is what a rotation script wants before it decides whether to wait.
Show output
1
Hand the ids to another command
fuser /srv/tips/app.log 2>/dev/null | xargs -r ps -o pid=,user=,comm= -p
fuser names processes by id and ps turns those back into something readable. -r stops xargs from running ps at all when the list is empty.
Show output
Your output will differ: the pid is this machine's
511 root tips-log
A whole filesystem
A path on its own asks about that one file. -m asks about the filesystem it sits on, which is the question behind umount: target is busy.
Find out what is keeping a mount busy
fuser -mv /srv/cache 2>&1 >/dev/null
F.c.. on both rows: a file open for writing, and a working directory, each of them on this filesystem. The mount row is the kernel's own reference to it and is there on every mounted filesystem.
Show output
USER PID ACCESS COMMAND
/srv/cache: root mount /srv/cache
root F.c.. tips-tmp
root F.c.. tips-archiver
Get the ids of everything on a filesystem
fuser -m /srv/cache 2>/dev/null
The list -k would act on. -m reaches every process with anything at all open on the device, so this is worth reading before it is worth piping anywhere.
Show output
Your output will differ: the pids are this machine's
55 64
Count what a filesystem is busy with
fuser -m /srv/cache 2>/dev/null | wc -w
The filesystem is held open by two processes. A number is enough for a script deciding whether to retry an umount, and it avoids parsing a table whose columns move with the length of the path.
Show output
2
Refuse to answer unless the path is a mount point
fuser -Mv /srv/tips 2>&1 >/dev/null; echo "exit $?"
-M makes the command fail rather than widen a path you thought was a mount point into the filesystem above it. Worth pairing with -m in a script, where /srv/cache failing to mount would otherwise turn a targeted kill into a machine-wide one.
Show output
Specified filename /srv/tips is not a mountpoint.
exit 1
Ports
-n tcp or -n udp switches the name space from files to sockets, and the argument becomes a port number. -4 and -6 narrow it to one address family.
Find the process listening on a port
fuser -vn tcp 8080 2>&1 >/dev/null
The answer to an address already being in use. Only the listener is reported, not the clients connected to it, which is where lsof says more.
Show output
USER PID ACCESS COMMAND
8080/tcp: root F.... tips-api
Check whether a port is free
fuser -vn tcp 22 2>&1 >/dev/null; echo "exit $?"
Nothing is bound to 22, so nothing is printed and the status is 1. A script picking a port can loop on this instead of binding and catching the failure.
Show output
exit 1
Count what is bound to a port
fuser -n tcp 8080 2>/dev/null | wc -w
A single holder. More than one means something is sharing the socket, through SO_REUSEPORT or through a parent that forked after binding.
Show output
1
Killing what holds it
-k sends a signal to every process the search found, SIGKILL unless another is named. There is no dry run, so run the same command without -k first and read what comes back.
Kill whatever has a file open
fuser -kv /srv/cache/site.tar 2>&1 >/dev/null
sleep 1
fuser -s /srv/cache/site.tar; echo "still held: $?"
-v alongside -k reports what is being killed as it goes, which is the closest this command comes to telling you what it did. The status of 1 afterwards confirms nothing holds the file any more.
Show output
USER PID ACCESS COMMAND
/srv/cache/site.tar: root F.... tips-archiver
still held: 1
Be asked before each process is killed
yes n | fuser -ikv /srv/cache/site.tar 2>&1 >/dev/null; echo "exit $?"
sleep 1
fuser -s /srv/cache/site.tar; echo "still held: $?"
-i asks per process and takes silence for no, so answering n leaves everything running. The prompt goes to standard error along with the table, which is why it lands in the middle of this one.
Show output
Your output will differ: the pid in the prompt is this machine's
USER PID ACCESS COMMAND
/srv/cache/site.tar: root F.... tips-archiver
Kill process 1346 ? (y/N) exit 1
still held: 0
Send a signal the process can handle
fuser -k -TERM /srv/tips/app.log >/dev/null 2>&1; echo "exit $?"
sleep 1
fuser -s /srv/tips/app.log; echo "still held: $?"
The default is SIGKILL, which no process can catch, so a service with a cleanup handler never runs it. Naming -TERM gives it the chance, and kill covers what the difference costs.
Show output
exit 0
still held: 1
Spare the processes that are only reading
fuser -kw /srv/tips/queue.dat >/dev/null 2>&1; echo "exit $?"
sleep 1
fuser -uv /srv/tips/queue.dat 2>&1 >/dev/null
-w kills only processes with write access, and tips-job has the file open read-only, so nothing is signalled and the status is 1. It still appears in the listing afterwards, since -w restricts what gets the signal rather than what gets found.
Show output
exit 1
USER PID ACCESS COMMAND
/srv/tips/queue.dat: user f.... (user)tips-job
Free a port by killing what is bound to it
fuser -kvn tcp 8080 2>&1 >/dev/null
sleep 1
fuser -sn tcp 8080; echo "still bound: $?"
Faster than finding the pid and killing it by hand, and correspondingly easier to point at the wrong port. The listing -v produces is the only confirmation of what went.
Show output
USER PID ACCESS COMMAND
8080/tcp: root F.... tips-api
still bound: 1
Clear a filesystem so it can be unmounted
fuser -kmv /srv/cache 2>&1 >/dev/null
sleep 1
fuser -sm /srv/cache; echo "still busy: $?"
This one kills everything with anything open on the device, including a shell whose working directory happens to be inside it. On a real mount point run it without -k first, and read every row.
Show output
USER PID ACCESS COMMAND
/srv/cache: root mount /srv/cache
root F.c.. tips-tmp
root F.c.. tips-archiver
still busy: 1