Here's the method I came up with, based on Greg's Wiki and some help from #bash on irc.freenode.net:
#!/bin/bash
trap 'rm -f manager; kill 0' EXIT
mkfifo manager
declare -A pids
restart () {
# assuming your servers/daemons are programs "a" and "b"
[[ -n ${pids[a]} ]] && kill "${pids[a]}"
[[ -n ${pids[b]} ]] && kill "${pids[b]}"
run_and_tell manager a & pids[a]=$!
run_and_tell manager b & pids[b]=$!
}
restart
while :; do
read < manager
restart
done
and run_and_tell:
#!/bin/bash
trap 'kill $pid' EXIT
manager=$1
prog=$2
$prog & pid=$!
wait $pid
echo >"$manager"
Not as nice as the bash 4.3 version, but it seems to work (e.g. testing with "sleep 9999" in run_and_tell). One annoyance is that I have to trap 'kill $pid' EXIT
in the runner, and it seems I have to do the same in $prog, to ensure it's killed when its parent is killed.
Here's an alternative version that avoids having to trap, by putting run_and_tell in its own process group:
#!/bin/bash
# The trap now needs to kill all created process groups:
trap 'rm -f manager; kill 0; kill ${pids[a]} ${pids[b]}' EXIT
mkfifo manager
declare -A pids
restart () {
# assuming servers/daemons are programs "a" and "b":
[[ -n ${pids[a]} ]] && kill -TERM -"${pids[a]}"
[[ -n ${pids[b]} ]] && kill -TERM -"${pids[b]}"
setsid ./run_and_tell manager a & pids[a]=$!
setsid ./run_and_tell manager b & pids[b]=$!
}
restart
while :; do
read < manager
restart
done
and run_and_tell becomes just:
#!/bin/bash
manager=$1
prog=$2
$prog
echo >"$manager"