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