If you run backups, report jobs, or pollers from cron or systemd, you eventually need two different checks: whether some PID is still alive on the system, and whether your own script is already running so a second start does not pile on top of the first. The sections below cover both, with patterns I use on real servers.
Tested all commands and short examples from this article on Ubuntu 25.04, kernel 6.14.0-37-generic, Bash 5.2.37.
Bash: check if a PID is still running (kill -0)
On Linux the usual test is kill -0: it sends no signal, but the kernel still checks
whether you may signal that process. If the PID does not exist, the shell prints an
error and returns non-zero.
pid=12345
if kill -0 "$pid" 2>/dev/null; then
echo "PID $pid is running"
else
echo "PID $pid is not running (or not visible to this user)"
fiExample when the PID is invalid:
bash: line 1: kill: (99999999) - No such processYou can pair this with /proc/$pid on Linux ([[ -d /proc/$pid ]]): if the
directory is missing, the process is gone. That avoids parsing kill’s stderr when
you only need a yes/no.
Linux: check if a process is running by name (pidof, pgrep)
When you care about a program name rather than a PID you already saved, pidof
prints matching PIDs (GNU/Linux). That covers the usual linux check if program is
running style question when the binary name is stable (systemd, sshd, your own
compiled service).
pids=$(pidof systemd)
if [[ -n "$pids" ]]; then
echo "systemd is running with PID(s): $pids"
else
echo "no systemd processes found (unusual on a normal Linux box)"
fiOn the machine used for testing, that printed:
systemd is running with PID(s): 4970pgrep matches the command line, which is flexible but easier to get wrong: a
pattern that is too short can match unrelated processes. Prefer an explicit pattern
and read man pgrep for -f, -x, and -u before using this in production.
Bash: check if the same script is already running (flock, recommended)
For bash check if script is already running, a file lock with flock is the
approach I reach for first. The kernel serializes lock holders; you do not have to
guess how many pgrep matches are “really” your script, and you do not leave stale
PID files behind when a node reboots mid-run.
Put this near the top of the script (after set options you want), pointing
LOCK at a path your user can write (often under /var/tmp or a dedicated app
directory):
#!/usr/bin/env bash
set -euo pipefail
LOCK=/var/tmp/my-backup-job.lock
exec 9>"$LOCK"
if ! flock -n 9; then
echo "Another instance is already running; exiting."
exit 1
fi
# ... rest of script ...flock -n is non-blocking: if another instance holds the lock, you exit immediately.
When the script ends, the lock is released automatically when the file descriptor
closes.
PID files: when they help, and how to avoid stale locks
Some daemons still use a PID file under /run or /var/run. That pattern answers
bash check if process exists once you have written the PID—but the file is a
cache, not the truth. If the script dies without unlinking the file, the next start
must detect a stale PID (process gone) and replace the file.
A minimal pattern:
- If the PID file exists, read the PID and run
kill -0(or check/proc/$pid). - If that PID is dead, remove the PID file and continue.
- After you know you are the sole runner, write your
$$(or the long-lived child’s PID) to the file. - Use a
trapsoEXITand common signals remove or update the file; see capture Ctrl+C in bash for cleanup patterns.
Never pgrep your script’s basename, write every match into a PID file, and delete
that file at the end of the same run—the older snippets that did that were easy to
misread and could fight concurrent legitimate runs.
Related: parallel jobs
If your goal is the opposite—run several workers at once and collect exit status—use the patterns in run shell scripts in parallel and collect exit status instead of a single-instance lock.
Summary
Use kill -0 (or /proc/$pid) when you already have a PID and need to
know if that Linux process is still running. Use pidof or a carefully chosen
pgrep when you are matching by program name. For bash check if script is
already running, prefer flock on a dedicated lock file so only one instance
executes and you do not depend on fragile basename matching. If you must use a PID
file, treat it as a hint: verify the PID is alive, clean up on exit with trap,
and delete stale files when kill -0 fails.

