I'm new to shell scripting. I have the following task to solve: I need to write a script that works with zsh and bash, where I can run 8 given scripts parallel.
The script that I wrote does the job, but once the script is running I can't stop these 8 subscripts properly. So my question is: How can I achieve that when I quit my script with CRTL C, all running subscripts get killed as well?
My Script:
#!/bin/bash
echo "This runs 8 similar versions of giza"
cd giza_google
sh runAgent.sh &
cd ..
cd giza
sh runAgent.sh &
cd ..
cd giza_1
sh runAgent.sh &
cd ..
cd giza_2
sh runAgent.sh &
cd ..
cd giza_3
sh runAgent.sh &
cd ..
cd giza_4
sh runAgent.sh &
cd ..
cd giza_5
sh runAgent.sh &
cd ..
cd giza_6
sh runAgent.sh
cd ..
Context: "giza" is an agent name and the directory structure should not be changed. Each folder contains a runAgent.sh that starts a simulation agent.
What I have already tried:
trap 'kill $(jobs -p)' EXIT
trap "exit" INT TERM ERR
trap "kill 0" EXIT
#!/bin/bash
trap ctrl_c INT
function ctrl_c() {
echo "Trapped CTRL_C"
kill -KILL $PID1 $PID2 $PID3 $PID4 $PID5 $PID6 $PID7
}
echo "This runs 8 similar versions of giza"
cd giza_google
sh runAgent.sh &
PID1=$!
echo "PID1=" $PID1
cd ..
cd giza
sh runAgent.sh &
PID2=$!
echo "PID2=" $PID2
cd ..
cd giza_1
sh runAgent.sh &
PID3=$!
echo "PID3=" $PID3
cd ..
cd giza_2
sh runAgent.sh &
PID4=$!
echo "PID4=" $PID4
cd ..
cd giza_3
sh runAgent.sh &
PID5=$!
echo "PID5=" $PID5
cd ..
cd giza_4
sh runAgent.sh &
PID6=$!
echo "PID6=" $PID6
cd ..
cd giza_5
sh runAgent.sh &
PID7=$!
echo "PID7=" $PID7
cd ..
cd giza_6
sh runAgent.sh
cd ..
CodePudding user response:
Just negate the pgid when you specify the pid to kill. You should be able to use $$
as the process group, but sometimes it's convenient to pull the process group id from ps
. Try:
#!/bin/bash
echo "This runs 8 similar versions of giza"
pgid=$(ps -o pgid= $$ | tr -d ' ')
trap 'kill -- -$pgid' 0
for d in giza_google giza giza_{1..6}; do
(cd $d; sh runAgent) &
done
wait
Note that I've changed the name of the script to runAgent
without the .sh
suffix. See https://www.talisman.org/~erlkonig/documents/commandname-extensions-considered-harmful/
CodePudding user response:
Insofar as you're using bash, arrays are available to you. "Append to this array" syntax doesn't require all the manual control over numbering involved in having a separate array per child, and you can expand your complete array's contents onto a kill
command when you want to shut everything down.
#!/usr/bin/env bash
agent_pids=( )
(cd giza_a && exec ./runAgent) & agent_pids =( "$!" )
(cd giza_b && exec ./runAgent) & agent_pids =( "$!" )
(cd giza_c && exec ./runAgent) & agent_pids =( "$!" )
wait "${agent_pids[@]}" # let them all finish
kill "${agent_pids[@]}" # shut them all down
Using exec
consumes the subshell that the cd
was scoped to, and ensures that the PID you're storing is actually that of runAgent
, not the subshell that started it.
Of course, you can do all this in a function:
#!/usr/bin/env bash
shutdown() { kill "${agent_pids[@]}"; }
trap shutdown EXIT
agent_pids=( )
start_agent() {
for dir; do
(cd "$dir" && exec ./runAgent) & agent_pids =( "$!" )
done
}
start_agent giza_a giza_b giza_c
...and you can even use an associative array so you build a map from directories to their associated agents:
#!/usr/bin/env bash
declare -A agent_pids=( )
shutdown() { kill "${agent_pids[@]}"; }
trap shutdown EXIT
start_agent() {
for dir; do
(cd "$dir" && exec ./runAgent) & agent_pids[$dir]=$!
done
}
start_agent giza_a giza_b giza_c
for agent_name in "${!agent_pids[@]}"; do
agent_pid=${agent_pids[$agent_name]}
wait "$agent_pid"; agent_rc=$?
echo "Agent $agent_name with PID $agent_pid exited with status $agent_rc"
done
CodePudding user response:
You might consider using GNU Parallel to run jobs in parallel. Your whole script becomes:
parallel 'cd {} && ./runAgent' ::: giza*/