Bash running multiple program and handling close

Collect the process ID's, kill the background processes on exit.

killbg() {
        for p in "${pids[@]}" ; do
                kill "$p";
trap killbg EXIT
background job 1 & 
background job 2... & 
foreground job

Trapping EXIT runs the function when the shell exits, regardless of the reason. You could change that to trap killbg SIGINT to only run it on ^C.

This doesn't check if one of the background processes exited before the script tries to shoot them. If they do, you could get errors, or worse, shoot the wrong process.

Or kill them by job id. Let's read the output of jobs to find out which ones are still active.

killjobs() {
    for x in $(jobs | awk -F '[][]' '{print $2}' ) ; do 
        kill %$x
trap killjobs EXIT
sleep 999 &
sleep 1 &
sleep 999 &
sleep 30

If you run background processes that spawn other processes (like a subshell: (sleep 1234 ; echo foo) &), you need to enable job control with set -m ("monitor mode") for this to work. Otherwise just the lead process is terminated.

I was just reading similar questions about collecting PID's and then killing them all at the end of a script - the problem is that a PID for a finished process could get recycled and re-used in a new process before your script finishes, and then you could kill a new (random) process.

Trap EXIT and kill with bash's job control

You could use bash's job control to only kill processes started in the script with a trap and %n jobspec, counting the max number of jobs that could be running (only 3 in this example):

#trap 'kill %1 %2 %3' 0     # 0 or EXIT are equivalent
#trap 'kill %1 %2 %3' EXIT  # or use {1..n} as below
trap 'kill %{1..3}' EXIT
sleep 33 &
sleep 33 &
sleep 33 &
echo processes are running, ctrl-c the next sleep
sleep 66
echo this line will never be executed

Any extra "kills" to non-existent jobspec's that have already finished only results in an error message, it won't kill any other new/random processes.

kill your script's complete process group

Here's a slightly different way way to kill your script's complete process group. But if your script/shell's job control isn't set up then it could inherit it's PPID from it's parent... but without job control the above wouldn't work either.

The difference is this kill command for trap, using bash's PID, since it becomes the PGID for new processes:

trap 'kill -- -$$' EXIT

See this related Q or here where Johannes 'fish' Ziemke traps SIGINT and SIGTERM and uses setsid to "kill the complete process group in a new process group so we won't risk killing our selves.")