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 namespace
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:
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).
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:
sh -c 'sleep 10m' & ps -jH
shows that thesh
command has a differentPGID
from the calling shell.