Como aguardar em um script bash por vários subprocessos gerados nesse script para concluir e retornar o código de saída! = 0 quando qualquer um dos subprocessos termina com o código! = 0?
Script simples:
#!/bin/bash
for i in `seq 0 9`; do
doCalculations $i &
done
wait
O script acima aguardará todos os 10 subprocessos gerados, mas sempre dará o status de saída 0 (consulte help wait
). Como posso modificar esse script para descobrir os status de saída dos subprocessos gerados e retornar o código de saída 1 quando qualquer um dos subprocessos termina com o código! = 0?
Existe alguma solução melhor para isso do que coletar PIDs dos subprocessos, aguardá-los em ordem e somar os status de saída?
wait -n
, disponível no bash moderno para retornar somente quando o primeiro / próximo comando for concluído.wait -n
tem um pequeno problema: se não houver trabalhos filhos restantes (condição de corrida), ele retornará um status de saída diferente de zero (falha) que pode ser indistinguível de um processo filho com falha.Respostas:
wait
também (opcionalmente) leva o PID do processo para aguardar e com $! você obtém o PID do último comando lançado em segundo plano. Modifique o loop para armazenar o PID de cada subprocesso gerado em uma matriz e, em seguida, faça o loop novamente esperando em cada PID.fonte
for i in $n_procs; do ./procs[${i}] & ; pids[${i}]=$!; done; for pid in ${pids[*]}; do wait $pid; done;
certo?http://jeremy.zawodny.com/blog/archives/010717.html :
fonte
jobs -p
está fornecendo PIDs de subprocessos que estão no estado de execução. Irá pular um processo se o processo terminar antes dejobs -p
ser chamado. Portanto, se algum subprocesso terminar antesjobs -p
, o status de saída desse processo será perdido.jobs -p
não fornece PIDs de subprocessos, mas GPIDs . A lógica de espera parece funcionar de qualquer maneira, sempre espera no grupo se esse grupo existe e é ridículo se não, mas é bom estar ciente .. especialmente se alguém se basear nisso e incorporar algo como enviar mensagens ao subprocesso no qual caso a sintaxe seja diferente dependendo se você tem PIDs ou GPIDs. iekill -- -$GPID
vskill $PID
Aqui está um exemplo simples usando
wait
.Execute alguns processos:
Então espere por eles com o
wait
comando:Ou apenas
wait
(sem argumentos) para todos.Isso aguardará a conclusão de todos os trabalhos em segundo plano.
Se a
-n
opção for fornecida, aguarda o término do próximo trabalho e retorna seu status de saída.Veja:
help wait
ehelp jobs
para sintaxe.No entanto, a desvantagem é que isso retornará apenas o status do último ID, portanto, é necessário verificar o status de cada subprocesso e armazená-lo na variável.
Ou faça sua função de cálculo para criar algum arquivo com falha (vazio ou com log de falhas) e verifique esse arquivo, se existir, por exemplo
fonte
sleep 20 && true
esleep 20 && false
- ie: substitua-os pelas suas funções. Para entender&&
e||
, executeman bash
e digite '/' (pesquisa) e '^ * Listas' (um regex) e digite: man rolará para baixo até a descrição de&&
e||
||
STDERR também falhe.wait
Se você possui o GNU Parallel instalado, você pode:
O GNU Parallel fornecerá o código de saída:
0 - Todos os trabalhos foram executados sem erro.
1-253 - Alguns dos trabalhos falharam. O status de saída fornece o número de tarefas com falha
254 - Mais de 253 trabalhos falharam.
255 - Outro erro.
Assista aos vídeos de introdução para saber mais: http://pi.dk/1
fonte
doCalculations
é uma função definida no mesmo script (embora o OP não tenha sido claro sobre esse requisito). Quando tento,parallel
diz/bin/bash: doCalculations: command not found
(diz isso 10 vezes para oseq 0 9
exemplo acima). Veja aqui uma solução alternativa.xargs
possui alguma capacidade para iniciar trabalhos em paralelo por meio da-P
opção A partir daqui :export -f doCalculations ; seq 0 9 |xargs -P 0 -n 1 -I{} bash -c "doCalculations {}"
. Limitações dexargs
são enumeradas na página de manual paraparallel
.doCalculations
depender de outras variáveis de ambiente internas do script (personalizadasPATH
, etc.), elas provavelmente precisam ser explicitamenteexport
editadas antes do lançamentoparallel
.wget -O - pi.dk/3 | sh
você não terá confusões. Se o seu empacotador estragou tudo para você, incentivo-o a levantar o problema com ele. Variáveis e funções devem ser exportados (-f exportação) para o GNU Paralela para vê-los (verman parallel
: gnu.org/software/parallel/... )Que tal simplesmente:
Atualizar:
Conforme apontado por vários comentaristas, o item acima espera que todos os processos sejam concluídos antes de continuar, mas não sai e falha se um deles falhar; isso pode ser feito com a seguinte modificação sugerida por @Bryan, @SamBrightman e outros :
fonte
for pid in $pids; do wait $pid; done
help wait
- com vários IDswait
retorna apenas o código de saída do último, como @ vlad-frolov disse acima.wait
seja chamado? Acontece que isso não é um problema: se vocêwait
em um processo que já saiu,wait
sairá imediatamente com o status do processo já encerrado. (Obrigada,bash
autores!)Aqui está o que eu criei até agora. Gostaria de ver como interromper o comando de suspensão se uma criança terminar, para que não seja necessário ajustar
WAITALL_DELAY
o uso.fonte
Para paralelizar isso ...
Traduzir para este ...
--max-procs
base na quantidade de paralelismo que deseja (0
significa "de uma só vez").xargs
- mas nem sempre é instalado por padrão.for
loop não é estritamente necessário neste exemplo, poisecho $i
basicamente regenera apenas a saída de$(whatever_list
). Eu só acho que o uso dofor
palavra-chave facilita um pouco a visualização do que está acontecendo.Aqui está um exemplo de trabalho simplificado ...
fonte
--max-procs
: Como obter o número de CPUs / núcleos no Linux na linha de comando?Não acredito que seja possível com a funcionalidade incorporada do Bash.
Você pode receber uma notificação quando um filho sair:
No entanto, não há uma maneira aparente de obter o status de saída da criança no manipulador de sinais.
Obter esse status filho geralmente é o trabalho da
wait
família de funções nas APIs POSIX de nível inferior. Infelizmente, o suporte do Bash é limitado - você pode esperar por um processo filho específico (e obter seu status de saída) ou pode esperar por todos eles e sempre obter um resultado 0.O que parece impossível é o equivalente a
waitpid(-1)
, que bloqueia até o retorno de qualquer processo filho.fonte
Vejo muitos bons exemplos listados aqui, que também queriam incluir os meus.
Uso algo muito semelhante para iniciar / parar servidores / serviços em paralelo e verificar cada status de saída. Funciona muito bem para mim. Espero que isso ajude alguém!
fonte
trap "kill 0" EXIT
Isso é algo que eu uso:
fonte
O código a seguir aguardará a conclusão de todos os cálculos e retornará o status de saída 1 se algum dos doCalculations falhar.
fonte
Apenas armazene os resultados fora do shell, por exemplo, em um arquivo.
fonte
Aqui está minha versão que funciona para vários pids, registra avisos se a execução demorar muito e interrompe os subprocessos se a execução demorar mais do que um determinado valor.
Por exemplo, aguarde a conclusão de todos os três processos, registre um aviso se a execução demorar mais de 5 segundos, interrompa todos os processos se a execução demorar mais de 120 segundos. Não saia do programa com falhas.
fonte
Se você possui o bash 4.2 ou posterior, o seguinte pode ser útil para você. Ele usa matrizes associativas para armazenar nomes de tarefas e seus "códigos", assim como nomes de tarefas e seus pids. Também construí um método simples de limitação de taxa que pode ser útil se suas tarefas consumirem muito tempo de CPU ou E / S e você quiser limitar o número de tarefas simultâneas.
O script inicia todas as tarefas no primeiro loop e consome os resultados no segundo.
Isso é um pouco exagerado para casos simples, mas permite coisas bem legais. Por exemplo, pode-se armazenar mensagens de erro para cada tarefa em outra matriz associativa e imprimi-las depois que tudo se acalmar.
fonte
Acabei de modificar um script para segundo plano e paralelizar um processo.
Fiz algumas experiências (no Solaris com bash e ksh) e descobri que 'wait' gera o status de saída, se não for zero, ou uma lista de tarefas que retornam saídas diferentes de zero quando nenhum argumento PID é fornecido. Por exemplo
Bater:
Ksh:
Esta saída é gravada no stderr, portanto, uma solução simples para o exemplo dos OPs pode ser:
Enquanto isso:
também retornará uma contagem, mas sem o arquivo tmp. Isso também pode ser usado dessa maneira, por exemplo:
Mas isso não é muito mais útil que o arquivo tmp IMO. Não consegui encontrar uma maneira útil de evitar o arquivo tmp, além de evitar executar a "espera" em um subshell, o que geralmente não funciona.
fonte
Eu experimentei isso e combinei todas as melhores partes dos outros exemplos aqui. Esse script executará a
checkpids
função quando qualquer processo em segundo plano sair e produzirá o status de saída sem recorrer à pesquisa.fonte
set -m
permite que você use fg & bg em um scriptfg
, além de colocar o último processo em primeiro plano, tem o mesmo status de saída do processo em primeiro planowhile fg
interromperá o loop quando houverfg
saídas com status de saída diferente de zeroinfelizmente, isso não resolverá o caso quando um processo em segundo plano sair com um status de saída diferente de zero. (o loop não será encerrado imediatamente. aguardará a conclusão dos processos anteriores.)
fonte
Já existem muitas respostas aqui, mas estou surpreso que ninguém parece ter sugerido o uso de matrizes ... Então, aqui está o que eu fiz - isso pode ser útil para alguns no futuro.
fonte
Isso funciona, deve ser tão bom quanto não melhor que a resposta da @ HoverHell!
e, é claro, imortalizei esse script, em um projeto NPM que permite executar comandos bash em paralelo, úteis para testar:
https://github.com/ORESoftware/generic-subshell
fonte
trap $? $$
parece set código de saída a 0 e PID para shell atual em execução bash, cada vez para mimarmadilha é sua amiga. Você pode interceptar o ERR em muitos sistemas. Você pode interceptar EXIT ou DEBUG para executar um pedaço de código após cada comando.
Isso além de todos os sinais padrão.
fonte
o
set -e
parte superior faz com que seu script pare em falha.expect
retornará1
se algum subjob falhar.fonte
Exatamente para esse propósito, escrevi uma
bash
função chamada:for
.Nota :
:for
não apenas preserva e retorna o código de saída da função com falha, mas também finaliza toda a instância em execução paralela. O que pode não ser necessário neste caso.uso
for.sh
:Referências
fonte
Eu usei isso recentemente (graças a Alnitak):
A partir daí, é possível extrapolar facilmente e acionar (tocar em um arquivo, enviar um sinal) e alterar os critérios de contagem (contagem de arquivos tocados ou o que for) para responder a esse disparador. Ou se você quiser apenas 'any' diferente de zero rc, basta matar o bloqueio de save_status.
fonte
Eu precisava disso, mas o processo de destino não era um filho do shell atual; nesse caso
wait $PID
, não funciona. Eu encontrei a seguinte alternativa:Isso depende da presença de procfs , que pode não estar disponível (o Mac não fornece, por exemplo). Portanto, para portabilidade, você pode usar isso:
fonte
A captura do sinal CHLD pode não funcionar porque você pode perder alguns sinais se eles chegarem simultaneamente.
fonte
A solução para aguardar vários subprocessos e sair quando qualquer um deles sai com código de status diferente de zero é usando 'wait -n'
o código de status '127' é para processo inexistente, o que significa que o filho pode ter saído.
fonte
Aguarde todos os trabalhos e retorne o código de saída do último trabalho com falha. Diferentemente das soluções acima, isso não requer economia de pid. Apenas vá embora e espere.
fonte
Pode haver um caso em que o processo esteja concluído antes de aguardar o processo. Se acionarmos a espera por um processo que já está concluído, ele causará um erro como o pid não é filho desse shell. Para evitar esses casos, a seguinte função pode ser usada para descobrir se o processo está completo ou não:
fonte
Eu acho que a maneira mais direta de executar tarefas em paralelo e verificar o status é usando arquivos temporários. Já existem algumas respostas semelhantes (por exemplo, Nietzche-jou e mug896).
O código acima não é seguro para threads. Se você está preocupado com o fato de o código acima estar em execução ao mesmo tempo, é melhor usar um nome de arquivo mais exclusivo, como fail. $$. A última linha é atender ao requisito: "retornar código de saída 1 quando qualquer um dos subprocessos terminar com o código! = 0?" Eu joguei um requisito extra lá para limpar. Pode ter sido mais claro escrevê-lo assim:
Aqui está um trecho semelhante para reunir resultados de vários trabalhos: eu crio um diretório temporário, descrevo as saídas de todas as subtarefas em um arquivo separado e as despejo para revisão. Isso realmente não corresponde à pergunta - estou jogando isso como um bônus:
fonte
Eu quase caí na armadilha de usar
jobs -p
para coletar PIDs, o que não funciona se a criança já tiver saído, conforme mostrado no script abaixo. A solução que escolhi foi simplesmente chamarwait -n
N vezes, em que N é o número de filhos que tenho e que conheço deterministicamente.Resultado:
fonte