This is not a very known feature of the killing command but it is possible to kill a process along with all its children, this is very convenient when a script spawns into several processes and you want to terminate all of them at once.
The key is to pass the PGID (the process group ID) with a minus sign in front:
$ kill -9 -PGID
$ kill -- -PGID
Use 'ps -j' to list the processes’ PGIDs.
In the first example the SIGKILL (9) signal is sent to all the processes belonging to the process group ID denoted by PGID. When using PGID you must state the signal to be sent, if you want to send the default TERM (15) signal then you can use a double hyphen '--' as shown in the second example.
How does it work? Well, the first created process has as PGID the same value as its PID (process ID), afterwards each newly created child-process will inherit the PGID of the parent. Therefore, the most of the times you can simply execute:
kill -- -{PID_of_the_first_process}
To list the processes’ PPID and PGID use the command ps with the flags -f and -j respectively.
Let’s see a very simple example. We’ll create a C program that the only thing it does is to delay for some seconds and then call such program from a couple of scripts. These are the listings:
test.c
main(){ sleep(600); }
parent.sh
#!/bin/bash ./test parent & ./test parent & ./child.sh
child.sh
#!/bin/bash ./test child & ./test child & ./test child
Open a terminal, compile the C program and execute the parent.sh script:
$ gcc -o test test.c
$ ./parent.sh
Now, in another terminal let’s see the processes:
$ ps -efj | grep '[U]ID\|[p]arent\|[c]hild'
UID PID PPID PGID SID C STIME TTY TIME CMD
stewie 4562 3715 4562 3715 0 14:07 pts/6 00:00:00 /bin/bash ./parent.sh
stewie 4563 4562 4562 3715 0 14:07 pts/6 00:00:00 ./test parent
stewie 4564 4562 4562 3715 0 14:07 pts/6 00:00:00 ./test parent
stewie 4565 4562 4562 3715 0 14:07 pts/6 00:00:00 /bin/bash ./child.sh
stewie 4566 4565 4562 3715 0 14:07 pts/6 00:00:00 ./test child
stewie 4567 4565 4562 3715 0 14:07 pts/6 00:00:00 ./test child
stewie 4568 4565 4562 3715 0 14:07 pts/6 00:00:00 ./test child
What do we see here? We have two Bash scripts and five 'test' programs running. The parent.sh has PID 4562 which is the PGID of all the processes. Note that the 'test' programs started by 'child.sh' have a different PPID but the same PGID. If we wanted to terminate all the processes at once, it would not work to kill the parent process you can verify this by executing kill on the parent.sh PID:
$kill 4562
$ ps -efj | grep '^[U]ID\|[p]arent\|[c]hild'
UID PID PPID PGID SID C STIME TTY TIME CMD
stewie 4563 1 4562 3715 0 14:07 pts/6 00:00:00 ./test parent
stewie 4564 1 4562 3715 0 14:07 pts/6 00:00:00 ./test parent
stewie 4565 1 4562 3715 0 14:07 pts/6 00:00:00 /bin/bash ./child.sh
stewie 4566 4565 4562 3715 0 14:07 pts/6 00:00:00 ./test child
stewie 4567 4565 4562 3715 0 14:07 pts/6 00:00:00 ./test child
stewie 4568 4565 4562 3715 0 14:07 pts/6 00:00:00 ./test child
Only parent.sh was terminated, the rest of processes are still alive. Try now to use the PGID instead:
$ kill -- -4562
$ ps -efj | grep '^[U]ID\|[p]arent\|[c]hild'
UID PID PPID PGID SID C STIME TTY TIME CMD
Now we have killed them all, and yes, we are bad asses.
Finally, it is worth noting that your shell may have a built-in kill command, in fact the most common shells like bash, dash, mksh, zsh, tcsh do have it. If kill does not behave as expected it may be due to that and you should call kill using the full path (use which to determine it). The only universal way I am aware of for finding out if a command is built-in is to read the manual page (e.g. man bash); however shells usually have utilities to query the nature of a command, here is a small list
bash: | type -a kill |
mksh: | type -a kill (type is equivalent to whence -v) |
zsh: | type kill (type is equivalent to whence -v) |
dash: | type kill |
tcsh: | where kill |