Say hello to psgrep

By Filip Salo; published on June 05, 2007. Tags: linux

Now that we've done hero searching and tally ho-ing, let's move on to simplify another recurring shell task.

I somewhat regularly find myself doing something like ps aux | grep firefox, to see the CPU or memory usage of a process, or to find its pid in order to kill it. (There are other ways to accomplish each of those things, of course, but this is one way to do all three.)

The slight annoyances of this approach are, at the very least, twofold.

First, when I pipe to grep, I lose the column headers in the ps output, and don't think for a second I can actually remember what all those numbers mean without some guidance. I'm having a hard anough time interpreting them correctly with the headers.

I could pipe to grep 'firefox|COMMAND' to go around that, but that's ugly.

Second, when grep filters the output, it also matches its own line (since there's a "firefox" in "grep firefox".

I could pipe to grep [f]irefox to go around that - there's no "firefox" in "grep [f]irefox". But that's ugly, and every time you misuse a regexp like that, God kills a kitten.

So a better answer is to let ps do the filtering itself. The -p option lets you specify a comma-separated list of pids. The pgrep command, in turn, can look up processes based on their name. Its -d option lets you specify the delimiter (say, a comma).

The new command (ignoring output format for now) becomes:

$ ps -p "$(pgrep -d, firefox)"

That's a bit tedious to type out each time, so let's make a tiny script with some added candy:

#!/bin/sh
# psgrep - ps filtered by process command lines

# Make sure we got at least one argument (the pattern)
if [ $# -eq 0 ]; then
  echo "Usage: psgrep [options] PATTERN"
  exit 1
fi

# Use the last argument as the pattern, and grab any preceding
# arguments to pass them on to 'ps'.
PATTERN=${!#}
ARGS=${@::$#-1}

# Get the pids matching the given pattern
PIDS=$(pgrep -f -d, "$PATTERN")

# Simple error message if there were no matching processes
if [ -z "$PIDS" ]; then
  echo "No matching processes."
  exit
fi

# All seems fine; let's call ps.
ps ${ARGS[@]} -p "$PIDS"

Put it in the ~/bin/, and call it psgrep, and voila. You can now do psgrep firefox, and even add your favorite ps options, like psgrep -f firefox or psgrep uww firefox or whatnot.

Note that some options will select all processes, rendering the pid selection pretty useless. On the other hand, you could easily use that to turn this into a replacement wrapper for ps.

"Uhm, so what about the -C option", you ask. "Doesn't that do pretty much the same thing?".

Yep. Pretty much. I hadn't found it when I wrote this script, though. Also, that's why I use the -f switch to pgrep - that let's you do pattern matching on the full command line, not just the process name. If you only need exact matching on process names, go ahead and use ps -C NAME.

Enjoy.

Update: That -f thing kind of brings back the seconds annoyance mentioned above, doesn't it? It does. I should blog on a coffee high. But still.

Update 2: What do you mean, "let it go"? This is starting to look like line noise, but ditching the error handling could take us back to...

#!/bin/sh
# psgrep - ps filtered by process command lines

ps ${@::$#-1} -p $(pgrep -f "${!#}" | grep -v "$$")

..which seems to do it. (grep -v "$$" excludes the pid of the psgrep process itself - pgrep (confused yet?) never returns its own pid, but it happily returns the pid of psgrep.)

I'll shut up now.