'Trap and exit big shell script with background processes
I have 2 shell scripts that contain ffmpeg commands (command1.sh and command2.sh). command2.sh has like 500 ffmpeg commands that trigger one after another with ';', while command1.sh handles audio ffmpeg commands.
The main issue: It takes too much to kill the whole script and it takes away for 1-2 minutes the CPU power i need for another executing script so I'm losing CPU power for nothing, because i cannot kill it instantly.
Code: I have init.sh that contains :
trap 'print TERM received;exit' 15
chmod +x command1.sh;
chmod +x command2.sh;
./command1.sh & ./command2.sh
so it triggers both in background.
Then i execute pkill init.sh and i catch on the shell trap and exit the shell script but i get exitCode: 1, failed: true and the commands in the background still execute for 1 min until they get killed by another kill ${pid} which i execute after pkill.
Solution 1:[1]
shell kill parallelized subtasks
Some remarks:
chmod +x
is useless here. At all you could runsh command1.sh &
instead.- You have to kill all subtasks independantly
- As this question is tagged shell, my answer don't use bashisms. All script here is tested under bash, dash and busybox.
Something like:
#!/bin/sh
for cmd in ./command1.sh ./command2.sh;do
exec $cmd &
PIDS="$PIDS $!"
done
trap "kill $PIDS;exit" 15
wait
Of coure, between for cmd in
and ;do
, you could put as many commandXX.sh
you want (as long you keep line length into maximum supported by your installed OS)
Test script:
Here is a quick bash test script that sleep randomly between 2.0 to 12.99 seconds, then print done.
before exit:
#!/bin/bash
declare -i toSleep
case $1 in '' | *[!0-9]* ) toSleep='RANDOM%10+2' ;; * ) toSleep=$1 ;; esac
exec {dummy}<> <(:)
read -t $toSleep.$RANDOM -u $dummy _
echo done.
I've saved this into command1.sh
, chmod +x ..
and linked to command2.sh
...
More portable wrapper:
#!/bin/sh
for cmd in "$@";do
exec $cmd &
PIDS="$PIDS $!"
done
printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
$$ $(echo $PIDS|wc -w) "$PIDS"
trap "kill $PIDS;echo 'Process $$ killed.';exit" 15
wait
echo "Process $$ running $@ ended normally"
You could save this shell script into a file named simpleParallel.sh
, for sample, then:
chmod +x simpleParallel.sh
./simpleParallel.sh ./command1.sh ./command2.sh
You have to: kill -TERM 741297
to end 2 tasks: 741298 741299
Then if you kill -TERM 741297
from elsewhere,
Process 741297 killed.
But if you don't, you may read something like:
./simpleParallel.sh ./command1.sh ./command2.sh
You have to: kill -TERM 741968
to end 2 tasks: 741969 741970
done.
done.
Process 741968 running ./command1.sh ./command2.sh ended normally
Nota: If you send your kill command while one process is already done, you may see error message like:
./simpleParallel.sh: 1: kill: No such process
See bash version could avoid this bug.
Following this from another terminal console
Before running ./simpleParallel.sh
, you could run tty
without argument:
tty
/dev/pts/2
Then in a new free window, you could run:
watch ps --tty pts/2
while using another window again to run kill
command.
bash with some bashisms, now:
Under recent bash, there are lot of special feature.
- You could use Array to store backgrounded tasks's PIDs.
- From version 5.0, There is an
$EPOCHREALTIME
variable, which expands to the time in seconds since the Unix epoch with microsecond granularity. - From version 5.0, Associative arrays allow subscripts containing whitespace.
- From version 5.1,
wait
has a new[-p VARNAME]
option, which stores thePID
returned bywait -n
orwait
without arguments.
My script could become:
#!/bin/bash
declare -A PIDS ENDED
started=$EPOCHREALTIME
for cmd; do
exec "$cmd" &
PIDS["$cmd"]=$!
CMDS[$!]="$cmd"
done
printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
$$ ${#PIDS[@]} "${PIDS[*]}"
trapExit(){
kill "${PIDS[@]}"
printf 'Process %s + %d task killed: %s\n' $$ ${#PIDS[@]} "${!PIDS[*]}"
showDone
exit 1
}
trap trapExit 15
showDone() {
for cmd in "${!ENDED[@]}";do
read -r elap < <(bc -l <<<"${ENDED["$cmd"]}-$started")
printf "Elapsed: %.4f sec for %s\n" "$elap" "$cmd"
done
}
while ((${#PIDS[@]}));do if wait -n -p pid ;then
printf "Process %d done (%s).\n" "$pid" "${CMDS[pid]}"
ENDED["${CMDS[pid]}"]=$EPOCHREALTIME
unset PIDS["${CMDS[pid]}"] CMDS[pid]
fi; done
echo "Process $$ running $* ended normally"
showDone
Output could look like:
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1309816
to end 2 tasks: 1309817 1309818
done.
Process 1309817 done (./command1.sh).
done.
Process 1309818 done (./command2.sh).
Process 1309816 running ./command1.sh ./command2.sh ended normally
Elapsed: 3.1644 sec for ./command1.sh
Elapsed: 4.2488 sec for ./command2.sh
or
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1310031
to end 2 tasks: 1310032 1310033
done.
Process 1310033 done (./command2.sh).
done.
Process 1310032 done (./command1.sh).
Process 1310031 running ./command1.sh ./command2.sh ended normally
Elapsed: 9.1868 sec for ./command1.sh
Elapsed: 3.2310 sec for ./command2.sh
If you kill
them early:
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294577
to end 2 tasks: 1294578 1294579
Process 1294577 + 2 task killed: ./command1.sh ./command2.sh
If you kill
them later:
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294958
to end 2 tasks: 1294959 1294960
done.
Process 1294959 done (./command1.sh).
Process 1294958 + 1 task killed: ./command2.sh
Elapsed: 3.1779 sec for ./command1.sh
or
./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294971
to end 2 tasks: 1294972 1294973
done.
Process 1294973 done (./command2.sh).
Process 1294971 + 1 task killed: ./command1.sh
Elapsed: 6.9344 sec for ./command2.sh
and no error message about trying to kill unexistant pid.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |