1

I'd like to kill a process with a long name that ends with foo. Currently my plan is to use pkill (though for testing, to be friendly to the process, I've been checking my patterns with pgrep instead). I've seen this question that shows that for long process names, you must use -f to get the whole command line. The works great for getting the rest of the long process name; the problem is that then the pattern is matched against the whole command line rather than just the process name, and I would not like to accidentally kill a different program that happened to have foo as an argument.

So I believe I would like to write a pattern something like this:

pgrep -f '^[^\0]*foo'

where I've written \0 to mean a zero byte -- the separator that -f puts before each argument to a process. Of course \0 doesn't work; this pattern actually matches any sequence of characters that are not \ or (ASCII) 0 instead. I also tried this:

pgrep -f '^[^'`echo -n '\0'`']*foo'

Here I have confirmed that my shell produces a single 0 byte for echo -n '\0'. Unfortunately, because of the way arguments are passed, pgrep stops scanning a pattern when it sees an actual 0 byte, so this command behaves exactly the same as pgrep -f '^[^' would -- that is, it throws an error about receiving an invalid pattern.

How can I write a pattern that refers to a zero byte in it (or, well, any byte that isn't a zero byte)?

1 Answers1

1

pgrep -f matches on the concatenation with SPC characters of the arguments passed to the last command the process executed (as seen in /proc/pid/cmdline on Linux). That never contains NUL characters.

So you'd need to do:

pgrep -f '^[^ ]*foo( |$)'

Though you'd miss the processes that have executed some command with /path/with space/foo as argv[0] and it would report processes that have execute a command with foo bar as argv[0].

In any case, you can't pass a NUL in an argument to a command that is executed as command arguments are NUL-delimited strings.

In zsh, you can pass NUL to builtin commands and functions (as those are not affected by that limitation of execve() since they are not executed). So on Linux, you could do:

print -rC1 /proc/<->(Ne[$'[[ "${$(<$REPLY/cmdline)%%\0*}" = *foo ]]']:t)

to find the processes whose argv[0] ends in foo.

Or:

print -rC1 /proc/<->/exe(N-.e['[[ $REPLY:A = *foo ]]']:h:t)

For those that are executing a command whose path ends in foo (though you may be limited to your own processes).

On Linux, process names are limited to 15 bytes and are changed to the first 15 bytes of the basename of the first argument to each call they make to execve(), though can also be changed by writing to /proc/pid/comm or with prctl(PR_SET_NAME). Processes can also change what's in /proc/pid/cmdline. See for instance:

$ perl -lne 'BEGIN{$0 = "foo bar 90123456789"}; print "$ARGV: $_"' /proc/self/{cmdline,comm}
/proc/self/cmdline: foo bar 90123456789
/proc/self/comm: foo bar 9012345
  • Oof. Is there any correct way to match a pattern against the full process name and only the process name? – Daniel Wagner Sep 23 '20 at 17:00
  • (And yes, I know I can't pass an actual 0 byte, as I said in the question. But many programs have a way to pass some sequence of non-0 bytes that they choose to interpret as a 0 byte, which would be one way of getting where the question asked. Though, as this answer details, not where I need to go, unfortunately.) – Daniel Wagner Sep 23 '20 at 17:00
  • @DanielWagner, for the process name, use pgrep without -f. pgrep 'foo$'. But note that on Linux, the process name is limited to 15 bytes. – Stéphane Chazelas Sep 23 '20 at 18:06
  • This edit is fantastic, thanks! – Daniel Wagner Sep 23 '20 at 18:31