Dados dois comandos de segundo plano, termine o outro quando sair

12

Eu tenho um script simples que inicia dois servidores:

#!/bin/bash
(cd ./frontend && gulp serve) & (cd ./backend && gulp serve --verbose)

Se o segundo comando sair, parece que o primeiro comando continua em execução.

Como posso alterar isso para que, se um dos comandos sair, o outro seja encerrado?

Observe que não precisamos verificar os níveis de erro dos processos em segundo plano, apenas se eles foram encerrados.

    
por blah238 24.09.2015 / 00:47

4 respostas

17

Isso inicia os dois processos, aguarda o primeiro que termina e depois mata o outro:

#!/bin/bash
{ cd ./frontend && gulp serve; } &
{ cd ./backend && gulp serve --verbose; } &
wait -n
pkill -P $$

Como funciona

  1. Iniciar:

    { cd ./frontend && gulp serve; } &
    { cd ./backend && gulp serve --verbose; } &
    

    Os dois comandos acima iniciam os dois processos em segundo plano.

  2. Espere

    wait -n
    

    Isso aguarda a conclusão de qualquer trabalho em segundo plano.

    Por causa da opção -n , isso requer o bash 4.3 ou melhor.

  3. Mate

    pkill -P $$
    

    Isso mata qualquer trabalho para o qual o processo atual é o pai. Em outras palavras, isso elimina qualquer processo em segundo plano que ainda esteja em execução.

    Se o seu sistema não tiver pkill , tente substituir esta linha por:

    kill 0
    

    que também mata o grupo de processos atual .

Exemplo facilmente testável

Ao alterar o script, podemos testá-lo mesmo sem o gulp instalado:

$ cat script.sh 
#!/bin/bash
{ sleep $1; echo one;  } &
{ sleep $2; echo two;  } &
wait -n
pkill -P $$
echo done

O script acima pode ser executado como bash script.sh 1 3 e o primeiro processo termina primeiro. Alternativamente, pode-se executá-lo como bash script.sh 3 1 e o segundo processo terminará primeiro. Em qualquer caso, pode-se ver que isso funciona como desejado.

    
por 24.09.2015 / 01:21
1

Isso é complicado. Aqui está o que eu inventei; pode ser possível simplificar / simplificar:

#!/bin/sh

pid1file=$(mktemp)
pid2file=$(mktemp)
stat1file=$(mktemp)
stat2file=$(mktemp)

while true; do sleep 42; done &
main_sleeper=$!

(cd frontend && gulp serve           & echo "$!" > "$pid1file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat1file"; kill "$main_sleeper" 2> /dev/null) &
(cd backend  && gulp serve --verbose & echo "$!" > "$pid2file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat2file"; kill "$main_sleeper" 2> /dev/null) &
sleep 1
wait "$main_sleeper" 2> /dev/null

if stat1=$(<"$stat1file")  &&  [ "$stat1" != "" ]  &&  [ "$stat1" != 0 ]
then
        echo "First process failed ..."
        if pid2=$(<"$pid2file")  &&  [ "$pid2" != "" ]
        then
                echo "... killing second process."
                kill "$pid2" 2> /dev/null
        fi
fi
if [ "$stat1" = "" ]  &&  \
   stat2=$(<"$stat2file")  &&  [ "$stat2" != "" ]  &&  [ "$stat2" != 0 ]
then
        echo "Second process failed ..."
        if pid1=$(<"$pid1file")  &&  [ "$pid1" != "" ]
        then
                echo "... killing first process."
                kill "$pid1" 2> /dev/null
        fi
fi

wait
if stat1=$(<"$stat1file")
then
        echo "Process 1 terminated with status $stat1."
else
        echo "Problem getting status of process 1."
fi
if stat2=$(<"$stat2file")
then
        echo "Process 2 terminated with status $stat2."
else
        echo "Problem getting status of process 2."
fi
  • Primeiro, inicie um processo ( while true; do sleep 42; done & ) que dorme / pausa para sempre. Se você tem certeza de que seus dois comandos terminará dentro de um determinado período de tempo (por exemplo, uma hora), você pode mudar isso para um único sono que irá exceder esse (por exemplo, sleep 3600 ). Você poderia então alterar a seguinte lógica para usar isso como um tempo limite; Ou seja, mate os processos se eles ainda estiverem em execução depois de tanto tempo. (Observe que o script acima atualmente não faz isso.)
  • Inicie os dois processos assíncronos (plano de fundo simultâneo).
    • Você não precisa de ./ para cd .
    • %código% é uma construção complicada que inicia um processo de forma assíncrona, captura seu PID e aguarda; tornando-se um processo em primeiro plano (síncrono). Mas isso acontece dentro de uma lista command & echo "$!" > somewhere; wait "$!" que está em segundo plano na sua totalidade, Portanto, os processos (…) são executados de forma assíncrona.
    • Depois que um dos processos gulp sair, escreva seu status em um arquivo temporário e mate o processo "para sempre dormir".
  • gulp para proteger contra uma condição de corrida onde o primeiro processo de segundo plano morre antes que o segundo tenha a chance de escrever seu PID no arquivo.
  • Aguarde o término do processo de "dormir para sempre". Isso acontece após qualquer das saídas dos processos de sleep 1 , conforme indicado acima.
  • Veja qual processo de segundo plano foi finalizado. Se falhar, mate o outro.
  • Se um processo falhou e nós matamos o outro, espere o segundo finalizar e salve seu status em um arquivo. Se o primeiro processo terminou com sucesso, espere o segundo terminar.
  • Verifique o status dos dois processos.
por 24.09.2015 / 20:38
1

Para completar, aqui está o que acabei usando:

#!/bin/bash
(cd frontend && gulp serve) &
(cd backend && gulp serve --verbose) &
wait -n
kill 0

Isso funciona para mim no Git for Windows 2.5.3 de 64 bits. Versões mais antigas podem não aceitar a opção -n em wait .

    
por 24.09.2015 / 22:58
0

No meu sistema (Centos), wait não tem -n , então fiz isso:

{ sleep 3; echo one;  } &
FOO=$!
{ sleep 6; echo two;  } &
wait $FOO
pkill -P $$

Isso não espera por "qualquer um", e sim pelo primeiro. Mas ainda assim pode ajudar se você souber qual servidor será parado primeiro.

    
por 22.05.2018 / 01:38