6

I need to kill some processes that are run as sudo, matching them by full name, and count the number of original command invocations.

For each process there are two processes: the command itself, and the sudo one, but I can work this around:

$ sudo -b perf record sleep 100

$ pgrep -fa '/perf record'
2245700 /usr/lib/linux-tools/.../perf record sleep 100

The problem is that when I invoke sudo pkill, it will find, kill and count itself:

$ sudo pkill -ef '/perf record' | wc -l
2

Is there a simple way for pkill not to include itself, in this case?

I've tried using pid files, which could be acceptable, but the pgrep/pkill documentation is lacking, and it seems not to work in the basic form:

$ pgrep -f '/perf record' | tee /tmp/pids
$ sudo pkill -F /tmp/pids
 killed (pid 2249211)

as it will kill only the first. The documentation says:

       -F, --pidfile file
              Read PID's from file.  This option is perhaps more useful for pkill than pgrep.

but it's ambiguous about the meaning of PID's.

2
  • When you kill the process, wouldn't the sudo process exit automatically? Removing that complexity should make the stated problem easier to solve. If your sudo policy allows for it, you could do sudo true; sudo command, and then kill the command as normal.
    – jpaugh
    Commented Apr 12, 2021 at 19:21
  • Actually, killing by PID wouldn't necessarily work there, because it'd be the sudo's PID, not the inner process.
    – jpaugh
    Commented Apr 12, 2021 at 19:26

4 Answers 4

17

Try:

sudo pkill -ef '/[p]erf record' | wc -l

It's a trick which uses a character class containing only the single letter p.

The regex is looking for /perf, but the sudo pkill command line has /[p]erf, which doesn't match.

This method has been used for decades in commands like ps aux | awk '/[f]oo/ {print $1}'

6
  • 1
    BTW, you'll often see people perpetrating atrocities like ps aux | grep foo | grep -v grep | awk '{print $1}'. No. Just No.
    – cas
    Commented Apr 12, 2021 at 1:39
  • 4
    Weren't pkill and pgrep created so we wouldn't have to use tricks like this? I thought they always avoided matching themselves.
    – Barmar
    Commented Apr 12, 2021 at 15:05
  • @Barmar yeah, they were (also, I suspect, because most people had no idea ps's -C option even existed, and that's really only good for matching command names anyway, not args...which kind of sucks if you're looking for a python script or similar). Unfortunately, pgrep has almost non-existent output formatting options, and there are still lots of times you need to run stuff like ps -C command-name -o pid,%mem,args
    – cas
    Commented Apr 12, 2021 at 15:11
  • 4
    @Barmar they do avoid matching themselves. It's just that by being run through sudo they match the sudo own process, which is not the pgrep/pkill process. The sudo process command line, too, carries a string that matches a bare pattern like the OP's one.
    – LL3
    Commented Apr 12, 2021 at 16:32
  • @LL3 IMO your answer still has value. but i have no objection if you want to edit mine (and credit yourself for the -F stuff, of course).
    – cas
    Commented Apr 12, 2021 at 16:45
6

Note that the pattern in pkill/pgrep is an extended regex, like in grep -E. Hence you may just provide a regex that targets more precisely the wanted process(es). In your case one might be like:

sudo pkill -ef '^(/[^/]+)+/perf record'

or even better the trick described by @cas in his answer.

It may be worth explaining why all this is required:

It is well known that pkill/pgrep never match themselves. However, the criteria they use is by matching the PID, i.e. they check whether one of the matching PIDs is theirs and they exclude precisely that one.

But being run (with the -f full-command-line mode option) through a sudo, the pkill command ends up matching the sudo own process too, because that one, too, carries a command-line string that matches a bare pattern like the one you used. Obviously sudo own process is not the same PID as the pkill one, therefore pkill does not exclude that one from the list, thus killing the sudo process that spawned (and still holds) the pkill process.

Consider:

$ sudo -b perf record sleep 1234
$ sudo pgrep -fa '/perf record'
1446 /usr/lib/linux-tools/4.2.0-42-generic/perf record sleep 1234
1466 sudo pgrep -fa /perf record  # <-- if I issued a pkill, this process, the sudo, would have been killed

In the above snippet note how the pgrep's own process is indeed excluded by the list. But the sudo process also carries a command-line that matches the bare regex.

By providing a more tailored regex instead, pkill does not include the sudo pkill -ef <...> process anymore because the process's own command line, carrying the tailored regex, does not match the regex itself.


A final note about -F option.

At the time I am writing this, that option still reads only one single PID from the file. So yes, the documentation, as well as the program's --help output, are misleading. This comment in the sources says the ugly truth.

/* FreeBSD: the arg is a file containing a PID to match */

2

Get the pid directly, and avoid having to search for it:

pid="$(sudo bash -c 'sleep 100 >/dev/null & echo $!')"
sudo kill "$pid"

The redirection of stdout is necessary even if the background process doesn't output anything, because otherwise it'll be set to feed the result of $(), which would cause $() to hang while it tries to read from the subprocesses until the backgrounded task closes its stdout.

The main benefit here over pkill is that you'll always get the correct pid instead of possibly killing other processes that match your search. For example, if you run your script many times concurrently, there might be multiple perf processes that'll match [p]erf record, each a child of each invocation of the script.

3
  • There's probably a much less gnarly way to write that, but it's a really good technique.
    – jpaugh
    Commented Apr 12, 2021 at 19:10
  • @jpaugh Most gnarliness is from the use of sudo. Without sudo, one could remove the bash -c, the >/dev/null and the $().
    – JoL
    Commented Apr 12, 2021 at 19:19
  • This is a good idea, and it was indeed the first attempt. The problem is that was inside a backgrounded command group, and things got confusing in terms of processes tree, so I opted for a rougher but simpler approach.
    – Marcus
    Commented Apr 13, 2021 at 7:36
2

In the upcoming procps 4.0.1, the -A option is introduced to pkill and pgrep to ignore its ancestors.

That means that you can simply run the following to avoid killing your sudo process:

sudo pkill -Aef '/perf record'

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .