Kill all descendant processes
Update
This is one of those ones where I clearly should have read the question more carefully (though seemingly this is the case with most answers on to this question). I have left the original answer intact because it gives some good information, even though it clearly misses the point of the question.
Using SID
I think the most general, robust approach here (at least for Linux) is to use SID (Session ID) rather than PPID or PGID. This is much less likely to be changed by child processes and, in the case of shell script, the setsid
command can be used to start a new session. Outside of the shell the setuid
system call can be used.
For a shell that is a session leader, you can kill all the other processes in the session by doing (the shell won't kill itself):
kill $(ps -s $$ -o pid=)
Note: The trailing equals sign in argument pid=
removes the PID
column header.
Otherwise, using system calls, call getsid
for each process seems like the only way.
Using a PID namspace
This is the most robust approach, however the downsides are that it is Linux only and that it needs root privileges. Also the shell tools (if used) are very new and not widely available.
For a more detailed discussion of PID namespaces, please see this question - Reliable way to jail child processes using `nsenter:`. The basic approach here is that you can create a new PID namespace by using the CLONE_NEWPID
flag with the clone
system call (or via the unshare
command).
When a process in a PID namespace is orphaned (ie when it parent process finishes), it is re-parented to the top level PID namespace process rather than the init
. This means that you can always identify all the descendants of the top level process by walking the process tree. In the case of a shell script the PPID approach below would then reliably kill all descendants.
Further reading on PID namespaces:
- Namespaces in operation, part 3: PID namespaces
- Namespaces in operation, part 4: more on PID namespaces
Original Answer
Killing child processes
The easy way to do this in a shell script, provided pkill
is available is:
pkill -P $$
This kills all children of the current given process ($$
expands to the PID of the current shell).
If pkill
isn't available, a POSIX compatible way is:
kill $(ps -o pid= --ppid $$)
Killing all descendent processes
Another situation is that you may want to kill all the descendants of the current shell process as well as just the direct children. In this case you can use the recursive shell function below to list all the descendant PIDs, before passing them as arguments to kill:
list_descendants ()
{
local children=$(ps -o pid= --ppid "$1")
for pid in $children
do
list_descendants "$pid"
done
echo "$children"
}
kill $(list_descendants $$)
Double forks
One thing to beware of, which might prevent the above from working as expected is the double fork()
technique. This is commonly used when daemonising a process. As the name suggests the process that is to be started runs in the second fork of the original process. Once the process is started, the first fork then exits meaning that the process becomes orphaned.
In this case it will become a child of the init
process instead of the original process that it was started from. There is no robust way to identify which process was the original parent, so if this is the case, you can't expect to be able to kill it without having some other means of identification (a PID file for example). However, if this technique has been used, you shouldn't try to kill the process without good reason.
Further Reading:
- Why fork() twice
- What is the reason for performing a double fork when creating a daemon?
You can use:
kill -TERM -- -XXX
where XXX
is group number of process group you want to kill. You can check it using:
$ ps x -o "%p %r %c"
PID PGID COMMAND
2416 1272 gnome-keyring-d
2427 2427 gnome-session
2459 2427 lightdm-session <defunct>
2467 2467 ssh-agent
2470 2427 dbus-launch
2471 2471 dbus-daemon
2484 2427 gnome-settings-
2489 2471 gvfsd
2491 2471 gvfs-fuse-daemo
2499 2427 compiz
2502 2471 gconfd-2
2508 2427 syndaemon
2513 2512 pulseaudio
2517 2512 gconf-helper
2519 2471 gvfsd-metadata
For more details about process groups ID, you can see man setpgid
:
DESCRIPTION
All of these interfaces are available on Linux, and are used for get‐
ting and setting the process group ID (PGID) of a process. The pre‐
ferred, POSIX.1-specified ways of doing this are: getpgrp(void), for
retrieving the calling process's PGID; and setpgid(), for setting a
process's PGID.
setpgid() sets the PGID of the process specified by pid to pgid. If
pid is zero, then the process ID of the calling process is used. If
pgid is zero, then the PGID of the process specified by pid is made the
same as its process ID. If setpgid() is used to move a process from
one process group to another (as is done by some shells when creating
pipelines), both process groups must be part of the same session (see
setsid(2) and credentials(7)). In this case, the pgid specifies an
existing process group to be joined and the session ID of that group
must match the session ID of the joining process.
If you know the parent processes PID you can do this using pkill
.
Example
$ pkill -TERM -P 27888
Where the PPID is 27888.
excerpt from pkill man
-P, --parent ppid,...
Only match processes whose parent process ID is listed.
What's my PID in a script?
This is probably your next question so when in a Bash script you can find out the script's PID using $$
at the top.
Example
Say I have this script:
$ more somescript.bash
#!/bin/bash
echo "top: $$"
sleep 5
echo "bottom: $$"
Now I run it, backgrounded:
$ ./somescript.bash &
[2] 28007
top: 28007
Peeking at it with pgrep
shows we've got the right PID:
$ pgrep somescript.bash
28007
$ bottom: 28007
[2]+ Done ./somescript.bash
Using a process' PGID
If you use this ps
command you can find out a processes PGID, which you can kill using instead.
Using now this script, killies.bash
:
$ more killies.bash
#!/bin/bash
sleep 1000 &
sleep 1000 &
sleep 1000 &
sleep 100
We run it like so:
$ killies.bash &
Checking in on it:
$ ps x -o "%p %r %c"
PID PGID COMMAND
28367 28367 killies.bash
28368 28367 sleep
28369 28367 sleep
28370 28367 sleep
28371 28367 sleep
Now we kill the PGID:
$ pkill -TERM -g 28367
[1]+ Terminated ./killies.bash
Additional methods
If you take a look at this SO Q&A you'll find still more methods for doing what you want:
- Best way to kill all child processes
References
- How does a Linux/Unix Bash script know its own PID?