The super simple solution is to add this at the end of the script:
kill -- -$$
Explanation:
$$
gives us the PID of the running shell. So, kill $$
would send a SIGTERM to the shell process. However, if we negate the PID, kill
sends a SIGTERM to every process in the process group. We need the --
beforehand so kill
knows that -$$
is a process group ID and not a flag.
Note that this relies on the running shell being a process group leader! Otherwise, $$
(the PID) will not match the process group ID, and you end up sending a signal to who knows where (well, probably nowhere as there is unlikely to be a process group with a matching ID if we're not a group leader).
When the shell starts, it creates a new process group[1]. Every forked process becomes a member of that process group, unless they explicitly change their process group via a syscall (setpgid
).
The easiest way to guarantee a particular script runs as a process group leader is to launch it using setsid
. For example, I have a few of these status scripts which I launch from a parent script:
#!/bin/sh
wifi_status &
bat_status &
Written like this, both the wifi and battery scripts run with the same process group as the parent script, and kill -- -$$
doesn't work. The fix is:
#!/bin/sh
setsid wifi_status &
setsid bat_status &
I found pstree -p -g
useful to visualise process & process group IDs.
Thanks to everyone who contributed and made me dig a little deeper, I learnt stuff! :)
[1] is there other circumstances where the shell creates a process group? eg. on starting a subshell? i don't know...
To answer your question directly, the command
jobs -p
gives you the list of all child processes.
Alternative #1
But in your case it might be easier to just use the command wait
without any params:
first_program &
second_program &
wait
This will wait until ALL child processes have finished.
Alternative #2
Another alternative is using $!
to get the PID of the last program and perhaps accumulate in a variable, like so:
pids=""
first_program &
pids="$pids $!"
second_program &
pids="$pids $!"
and then use wait with that (this is in case you only want to wait for a subset of you child processes):
wait $pids
Alternative #3
OR, if you want to wait only until ANY process has finished, you can use
wait -n $pids
Bonus info
If you want a sigterm to your bash script to close your child processes as well, you will need to propagate the signal with something like this (put this somewhere at the top, before starting any process):
trap 'kill $(jobs -p)' SIGINT SIGTERM EXIT
Best Answer
I had the similar issue and something like the following helped me.
Read more on this Doc