Sample output
$ kill -0 4721 && echo alive || echo gone
alive
$ kill -0 4721 && echo alive || echo gone
bash: kill: (4721) - No such process
gone
The first run says the backup job on PID 4721 is still going. By the second run it has finished, and the error line tells you why the check failed: no such process. That reason matters, because there is a second way to fail that means the opposite.
When you would reach for it
A deploy script needs to know whether the daemon named in /run/app.pid is really running or whether the pidfile is stale from a crash. A long backup is running in another terminal and you want a loop that waits for it before starting the next step. wait only works on children of the current shell, and pgrep matches by name, which is the wrong question when you already know the exact PID you care about.
Gotchas
- Failing the check does not always mean the process is gone. Ask about a process owned by another user, without root, and the kernel refuses with EPERM: bash prints
kill: (1) - Operation not permittedand exits 1, the same status as a dead PID. Soecho goneis wrong for every root-owned daemon. Read the stderr message, or run the check as the process owner, before trusting a failure. - A zombie passes the check. A process that has exited but has not been reaped by its parent still holds its PID, so
kill -0succeeds on it.ps -o stat= -p 4721printsZfor that case. Children of your interactive shell get reaped almost immediately, but a buggy parent can leave zombies sitting for days. - PIDs get reused. On a busy box, or after a reboot, the number in a stale pidfile can belong to a completely different process, and
kill -0reports it alive without complaint. The check confirms that some process owns the PID, not that it is the one you meant. Pair it withps -o comm= -pwhen the answer matters. - The error text differs between shells. bash prints
kill: (4721) - No such process, dash sayskill: No such process, and zsh, the default on macOS, words it in its own way. Scripts should branch on the exit status, not by grepping the message, except to tell EPERM from a dead PID, where you have no other option short of root.
Variants
$ while kill -0 4721; do sleep 5; done; echo "4721 has exited"
Wait for a process your shell did not start. The loop ends on the first failed check, which prints one No such process line. If the first line says Operation not permitted instead, the process is not yours and the loop never waited at all
$ ps -o pid=,comm= -p "$(cat /run/app.pid)"
Check a pidfile and confirm the name at the same time. A stale pidfile whose PID was recycled shows up here as the wrong command name, which kill -0 cannot catch. The = after each column suppresses the header row
$ tail --pid=4721 -f /dev/null && echo "4721 has exited"
GNU tail as a blocking wait on any PID, no loop required. Linux only; on macOS this needs gtail from brew install coreutils