Entre os que testei, e o mais perto que posso saber, três shells fazem praticamente a coisa certa com relação a SIGCHLD e wait : yash , dash e mksh . Você vê, wait é supostamente interrompível; ao configurar um manipulador de sinal, você precisa que o manipulador esteja fazendo um wait() , um sleep() ou um read() portably (embora aparentemente sleep() possa se comportar estranhamente se a interrupção vier de uma chamada anterior para alarm() ) . Qualquer sinal (não bloqueado / ignorado) deve parar um wait() .
As implementações de shell de tais coisas não devem diferir muito na minha opinião, mas ... algumas fazem. Particularmente, bash se comporta como o pior de todos os bash , ksh93 , dash , mksh , yash ou zsh . zsh e ksh93 quase acertam a sequência a seguir, mas não preservam o status de saída do primeiro processo para sair. Não é terrível - embora zsh também reclame de ter sido convidado a wait no pid mais recente.
Veja o que eu fiz:
unset IFS
script=$(cat <<""
PS4="$0 + "
trap ' for p ### loop over bgd pids
do shift ### clear current pid
if kill -0 "$p" 2>/dev/null ### still running?
then set -- "$@" "$p" ### then append again
else wait "$p" ### else get return
exit "$(kill "$@")$?" ### kill others; exit
fi
done' CHLD ### wait til CHLD
for n in $(shuf -i 3-7) ### randomize order
do (sleep "$n";exit "$n")& set "$@" "$!" ### sleep 3 exits 3
done; set -x; wait ### debug, wait
)
O acima deve funcionar não apenas para matar todos os filhos de segundo plano restantes de um shell assim que um retorna, mas também para propagar o primeiro código de saída do filho retornado para o do shell pai. Ele deve funcionar porque wait deve retornar imediatamente com o status de saída de um processo em segundo plano, caso seja chamado para um processo filho que ainda não tenha sido aguardado. E porque o SIGCHLD é o que termina o primeiro wait o segundo wait deve marcar o tempo primeiro em que o primeiro filho retornado está realmente aguardado. Pelo menos, simplesmente, deveria ser. Quanto mais complicada a implementação do shell, porém, menos confiável essa lógica se mostra, parece.
Esse é o $script de cada um dos shells, quando eu fiz ...
for sh in yash zsh ksh bash mksh dash
do time "$sh" +m -c "$script" ### no job control
done
bash é o único shell que não sai dentro de três segundos. zsh e ksh93 both (na minha opinião, incorretamente) exit 0 , mas caso contrário, saia dentro de três segundos. Os outros exit 3 dentro de 3 segundos. Aqui estão os resultados do teste:
yash + wait
yash + shift
yash + wait 19111
yash + kill 19112 19113 19116 19117
yash + exit 3
real 0m3.013s
user 0m0.007s
sys 0m0.000s
zsh + wait
zsh + p=19124
zsh + shift
zsh + kill -0 19124
zsh + set -- 19125 19127 19129 19132 19124
zsh + p=19125
zsh + shift
zsh + kill -0 19125
zsh + wait 19125
zsh:wait:12: pid 19125 is not a child of this shell
zsh + kill 19127 19129 19132 19124
zsh + exit 0
real 0m3.023s
user 0m0.017s
sys 0m0.000s
ksh + wait
ksh + shift
ksh + kill -0 19137
ksh + 2> /dev/null
ksh + set -- 19138 19139 19140 19141 19137
ksh + shift
ksh + kill -0 19138
ksh + 2> /dev/null
ksh + wait 19138
ksh + kill 19139 19140 19141 19137
ksh + exit 0
real 0m3.018s
user 0m0.000s
sys 0m0.010s
bash + wait
real 0m7.018s
user 0m0.007s
sys 0m0.007s
mksh + wait
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19157
mksh + set -- 19158 19159 19160 19161 19157
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19158
mksh + set -- 19159 19160 19161 19157 19158
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19159
mksh + set -- 19160 19161 19157 19158 19159
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19160
mksh + set -- 19161 19157 19158 19159 19160
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19161
mksh + wait 19161
mksh + kill 19157 19158 19159 19160
mksh + exit 3
real 0m3.022s
user 0m0.003s
sys 0m0.000s
dash + wait
dash + shift
dash + kill -0 19165
dash + set -- 19166 19168 19170 19173 19165
dash + shift
dash + kill -0 19166
dash + wait 19166
dash + kill 19168 19170 19173 19165
dash + exit 3
real 0m3.008s
user 0m0.000s
sys 0m0.000s