Do bash, crie dois processos e saia de ambos se o irmão sair

4

Do bash, estou gerando dois processos. Esses dois processos dependem uns dos outros. Eu quero que ambos saiam se um deles morrer. Qual é a maneira mais limpa de fazer isso? Atualmente tenho o seguinte:

# start process a
/bin/program_a;
a_pid=$!

# start process b
/bin/program_b;
b_pid=$!

# kill process b if process a exits
wait $a_pid
echo "a_pid died, killing process b"
kill -9 $b_pid

Mas isso só ajuda o processo a sair se o processo morrer. Como eu também faço um processo de saída se o processo b morre?

    
por peskal 02.02.2016 / 21:18

2 respostas

7

com zsh :

pids=()
trap '
  trap - CHLD
  (($#pids)) && kill $pids 2> /dev/null
' CHLD

sleep 2 & pids+=$!
sleep 1 & pids+=$!
sleep 3 & pids+=$!

wait

(aqui usando sleep como comandos de teste).

Com bash , parece que o trap CHLD só é executado quando a opção m está ativada. Você não deseja iniciar seus trabalhos nessa opção, embora isso os execute em grupos de processos separados. Observe também que a redefinição do manipulador no manipulador não parece funcionar com o bash. Então, o bash equivalente seria algo como:

pids=()
gotsigchld=false
trap '
  if ! "$gotsigchld"; then
    gotsigchld=true
    ((${#pids[@]})) && kill "${pids[@]}" 2> /dev/null
  fi
' CHLD

sleep 2 & pids+=($!)
sleep 1 & pids+=($!)
sleep 3 & pids+=($!)

set -m
wait
set +m
    
por 02.02.2016 / 22:31
0

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
    
por 03.02.2016 / 05:23

Tags