Skip to content

Finding Processes with pgrep


You now know ps well enough to list every process on a system. The next question is almost always narrower than that: is a specific process running right now? Given everything you learned in the grep chapter in earlier sections, the obvious move is piping ps output into grep — and it’s worth understanding exactly why that instinct, while not wrong, has a well-known trap built into it before reaching for the tool actually built for this job.

The Obvious Approach

ps aux | grep sshd

This runs fine, most of the time, and shows you any process with “sshd” somewhere in its listing. Try it, though, and look closely at the output:

root       734  0.0  0.1  15234  3456 ?        Ss   09:00   0:00 /usr/sbin/sshd -D
you       2211  0.0  0.0   9876   728 pts/0    S+   10:42   0:00 grep sshd

That second line is the problem. The grep sshd command you just ran is itself a process, and its own command line literally contains the text “sshd” — so it matches its own search. Every time you use this pattern, you’ll see the grep process show up in its own results, indistinguishable at a glance from a genuine match.

Working Around It — Badly

A common patch is excluding grep itself from the results:

ps aux | grep sshd | grep -v grep

This works, technically, but it’s a workaround for a self-inflicted problem, not a real solution — and it’s a signal worth recognizing on sight. If you ever find yourself writing grep -v grep, that’s the moment to stop and reach for the purpose-built tool instead, which is exactly what the rest of this chapter covers.

A Second, Quieter Problem: Unintended Matches

Self-matching isn’t even the only issue. grep matches on any substring, anywhere in the line — including places you didn’t intend. Searching for ssh doesn’t just match sshd; it also matches a process named sshfs, or a completely unrelated process that happens to have “ssh” appear somewhere in one of its command-line arguments, like a file path. ps aux | grep ssh can easily return processes that have nothing to do with the SSH service you were actually looking for, with no warning that this happened.

pgrep: Built For Exactly This Question

pgrep searches running processes directly — no ps, no pipe, no risk of matching itself, because pgrep isn’t matching against a printed listing at all. It’s querying the process table directly and printing matching PIDs.

pgrep sshd

This prints just the PID (or PIDs) of any process whose name matches sshd — no self-match, no grep -v grep workaround needed, because there’s no grep process in the picture at all.

Useful Flags

FlagEffect
-lShow the process name alongside each PID
-aShow the full command line alongside each PID
-u <user>Only match processes owned by a specific user
-xRequire an exact match against the process name, not a substring
-fMatch against the full command line, not just the process name
pgrep -l sshd
734 sshd

Immediately clearer than scanning a ps listing by eye — you get exactly what matched and nothing else.

pgrep -u alice

Every process owned by alice, regardless of name — genuinely difficult to express cleanly with ps | grep, since grep has no concept of “the user column specifically.”

Solving The Substring Problem: -x And -f

Remember the ssh vs. sshfs problem from earlier? -x fixes it directly:

pgrep -x ssh

This only matches a process whose name is exactly sshsshd and sshfs are both excluded, since neither is an exact match. Without -x, pgrep still matches substrings by default, the same way grep does — the difference isn’t that pgrep is inherently stricter, it’s that pgrep offers a clean, one-flag way to be strict when you need to be, rather than requiring you to hand-craft a more precise regular expression yourself.

By default, pgrep only matches against a process’s name — a fairly short field, often just the binary name. If what you’re actually trying to find is something buried in the full command-line arguments — say, a Python script run as python3 /opt/scripts/backup.py — the process name is just python3, and searching for backup would find nothing by default. -f widens the search to the entire command line:

pgrep -f backup.py

This matches based on the full invocation, not just the short process name — genuinely necessary the moment you’re searching for scripts or specific arguments rather than well-known daemon names.

The Pattern Language Is Still Regex

Since pgrep is matching patterns, everything from the grep chapter still applies — ^, $, ., and the rest all work the same way here as they did there:

pgrep -f "^python3.*backup"

This is a genuinely useful combination once you’re comfortable with both pieces: -f to search the full command line, and a regex anchor to make sure you’re matching the start of the command rather than an incidental substring buried somewhere in its arguments.

A Useful Pattern: pgrep And readlink

Let’s say you remember a program you execute is hangaround. After a while you forget the location of this program. Yes you can use find but there is another way too. pgrep reports hangaround is there with PID output but it tells nothing about where it is (the path). Let’s see pgrep output:

pgrep -la hang
21780 ./hangaround
21787 ./hangaround

Since hangaround is some program you ran. The /proc/PID/exe has the exact link with you can read to:

readlink -f /proc/21780

Now gives you exact path of your executable:

/tmp/shared/hangaround

Note

This is more of development environment rather than production because then in production you usually keep your binaries at /usr/local/bin or ~/.local/bin which which can easily find.

When ps | grep Is Still Reasonable

This isn’t a blanket rule that ps aux | grep is always wrong. If what you actually want is the full, multi-column ps output — CPU, memory, everything — filtered down to matching lines, pgrep alone won’t give you that; it only returns PIDs (or names, with -l/-a). In that specific case, ps aux | grep -v grep | grep pattern is a reasonable, if slightly clunky, way to get a filtered table, not just a filtered list of PIDs.

The trap isn’t the pipe itself — it’s forgetting the self-match problem exists, or reaching for grep out of habit when pgrep -u/-x/-f would answer the actual question more precisely and with less typing.

What’s Next

pgrep answers “is a process with this name or pattern running.” It has nothing to say about a different, equally common question: “what process is holding this specific file open” or “what’s listening on this port.” That’s a different search entirely, covered next with lsof and fuser.

Last updated on