Dados dois comandos em segundo plano, encerre o restante quando sair

14

Eu tenho um script bash 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 mudar isso para que, se um comando sair, o outro for finalizado?

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

blah238
fonte
Por que não gulp ./fronend/serve && gulp ./backend/serve --verbose?
heemayl
serveé um argumento, não um arquivo, portanto, o diretório atual precisa ser definido.
precisa saber é o seguinte
1
Além disso, esses são processos de execução demorada que precisam ser executados simultaneamente, desculpe se isso não estava claro.
precisa saber é o seguinte

Respostas:

21

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. Começar:

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

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

  2. Esperar

    wait -n

    Isso aguarda o término do trabalho em segundo plano.

    Por causa da -nopção, 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 mata qualquer processo em segundo plano que ainda esteja em execução.

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

    kill 0

    que também mata o grupo de processos atual .

Exemplo facilmente testável

Alterando o script, podemos testá-lo mesmo sem gulpinstalado:

$ 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 3e o primeiro processo termina primeiro. Como alternativa, é possível executá-lo como bash script.sh 3 1e o segundo processo será encerrado primeiro. Em ambos os casos, pode-se ver que isso funciona como desejado.

John1024
fonte
Isso parece ótimo. Infelizmente esses comandos não funcionam no meu ambiente bash (msysgit). Isso é ruim por não especificar isso. Vou testá-lo em uma caixa Linux real, no entanto.
blah238 23/09/2015
(1) Nem todas as versões do bashsuporte a -nopção para o waitcomando. (2) Concordo 100% com a primeira frase - sua solução inicia dois processos, aguarda a conclusão do primeiro e depois mata o outro. Mas a pergunta diz: "... se um comando cometer erros , o outro será encerrado?" Acredito que sua solução não é o que o OP deseja. (3) Por que você mudou (…) &para { …; } &? As &forças da lista (comando de grupo) para ser executado em um subshell de qualquer maneira. IMHO, você adicionou personagens e possivelmente introduziu confusão (eu tive que olhar duas vezes para entender) sem nenhum benefício.
G-Man diz 'Restabelecer Monica'
1
John está correto, eles são servidores da Web que normalmente devem continuar em execução, a menos que ocorra um erro ou que sejam sinalizados para terminar. Portanto, acho que não precisamos verificar o nível de erro de cada processo, apenas se ele ainda está em execução.
precisa saber é o seguinte
2
pkillnão está disponível para mim, mas kill 0parece ter o mesmo efeito. Também atualizei meu ambiente Git for Windows e parece que wait -nfunciona agora, por isso estou aceitando esta resposta.
precisa saber é o seguinte
1
Interessante. Embora eu veja que esse comportamento está documentado para algumas versões do kill. a documentação no meu sistema não menciona. No entanto, kill 0funciona de qualquer maneira. Boa descoberta!
precisa saber é o seguinte
1

Isso é complicado. Aqui está o que eu inventei; pode ser possível simplificá-lo / racionalizá-lo:

#!/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ê tiver certeza de que seus dois comandos serão encerrados dentro de um certo período de tempo (por exemplo, uma hora), você pode alterar isso para um único sono que excederá esse valor (por exemplo, sleep 3600). Você pode alterar a lógica a seguir 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 (segundo plano simultâneo).
    • Você não precisa ./para cd.
    • command & echo "$!" > somewhere; wait "$!" é uma construção complicada que inicia um processo de forma assíncrona, captura seu PID e aguarda por ele; tornando-o como um processo em primeiro plano (síncrono). Mas isso acontece em uma (…)lista que está em segundo plano na sua totalidade, portanto, os gulpprocessos são executados de forma assíncrona.
    • Após a gulpsaída de qualquer um dos processos, grave seu status em um arquivo temporário e interrompa o processo de "suspensão permanente".
  • sleep 1 para proteger contra uma condição de corrida em que o primeiro processo em segundo plano morre antes que o segundo tenha a chance de gravar seu PID no arquivo.
  • Aguarde o processo "durma para sempre" para terminar. Isso acontece após a saída de qualquer um dos gulpprocessos, conforme indicado acima.
  • Veja qual processo em segundo plano terminou. Se falhar, mate o outro.
  • Se um processo falhou e matamos o outro, aguarde o segundo finalizar e salve seu status em um arquivo. Se o primeiro processo foi concluído com êxito, aguarde o término do segundo.
  • Verifique os status dos dois processos.
G-Man diz que 'restabelece Monica'
fonte
1

Para completar, eis 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. As versões mais antigas podem não aceitar a -nopção ativada wait.

blah238
fonte
1

No meu sistema (Centos), waitnão tem, -nentão eu fiz isso:

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

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

Ondra Žižka
fonte
Se waittem a -nopção ou não depende do shell você está usando, não a distribuição Linux.
Kusalananda