Atualmente, tenho um script que faz algo como
./a | ./b | ./c
Quero modificá-lo para que, se algum de a, b ou c sair com um código de erro, eu imprima uma mensagem de erro e pare em vez de enviar uma saída incorreta para frente.
Qual seria a maneira mais simples / limpa de fazer isso?
shell
error-handling
pipe
hugomg
fonte
fonte
&&|
"apenas continue o pipe se o comando anterior for bem-sucedido". Suponho que você também poderia ter, o|||
que significaria "continuar o pipe se o comando anterior falhar" (e possivelmente canalizar a mensagem de erro como o do Bash 4|&
).a
,b
,c
comandos não são executados sequencialmente, mas em paralelo. Em outras palavras, os fluxos de dados sequencialmente a partira
dec
, mas o efectivoa
,b
ec
comandos iniciar (aproximadamente) ao mesmo tempo.Respostas:
Se você realmente não deseja que o segundo comando prossiga até que o primeiro seja bem-sucedido, provavelmente será necessário usar arquivos temporários. A versão simples disso é:
O redirecionamento '1> & 2' também pode ser abreviado para '> & 2'; no entanto, uma versão antiga do shell MKS tratou incorretamente o redirecionamento de erro sem o '1' anterior, então usei essa notação inequívoca para confiabilidade por muito tempo.
Isso vaza arquivos se você interromper algo. A programação shell à prova de bomba (mais ou menos) usa:
A primeira linha de trap diz 'execute os comandos'
rm -f $tmp.[12]; exit 1
'quando qualquer um dos sinais 1 SIGHUP, 2 SIGINT, 3 SIGQUIT, 13 SIGPIPE ou 15 SIGTERM ocorrer, ou 0 (quando o shell sair por qualquer motivo). Se você estiver escrevendo um script de shell, o trap final só precisa remover o trap em 0, que é o trap de saída do shell (você pode deixar os outros sinais no lugar, pois o processo está prestes a terminar de qualquer maneira).No pipeline original, é viável para 'c' ler dados de 'b' antes de 'a' terminar - isso geralmente é desejável (dá trabalho a vários núcleos, por exemplo). Se 'b' é uma fase de 'classificação', então isso não se aplica - 'b' tem que ver todas as suas entradas antes de poder gerar qualquer uma das suas saídas.
Se você deseja detectar quais comandos falham, você pode usar:
Isso é simples e simétrico - é trivial estender para um pipeline de 4 ou N partes.
A experimentação simples com 'set -e' não ajudou.
fonte
mktemp
outempfile
.trap
, lembre-se de que o usuário sempre pode enviarSIGKILL
a um processo para encerrá-lo imediatamente; nesse caso, a armadilha não terá efeito. O mesmo vale para quando o sistema passa por uma queda de energia. Ao criar arquivos temporários, certifique-se de usarmktemp
porque isso colocará seus arquivos em algum lugar, onde serão limpos após uma reinicialização (geralmente em/tmp
).No bash você pode usar
set -e
eset -o pipefail
no início do seu arquivo. Um comando subsequente./a | ./b | ./c
falhará quando qualquer um dos três scripts falhar. O código de retorno será o código de retorno do primeiro script com falha.Observe que
pipefail
não está disponível no sh padrão .fonte
set
(para 4 versões diferentes no total) e ver como isso afeta a saída do últimoecho
.Você também pode verificar a
${PIPESTATUS[]}
matriz após a execução completa, por exemplo, se você executar:Em seguida,
${PIPESTATUS}
haverá uma matriz de códigos de erro de cada comando no tubo, portanto, se o comando do meio falhou,echo ${PIPESTATUS[@]}
conteria algo como:e algo como isto é executado após o comando:
permitirá que você verifique se todos os comandos no pipe foram bem-sucedidos.
fonte
#!/bin/sh
, porque sesh
não for o bash, não funcionará. (Facilmente corrigível, lembrando-se de usar em#!/bin/bash
vez disso.)echo ${PIPESTATUS[@]} | grep -qE '^[0 ]+$'
- retorna 0 se$PIPESTATUS[@]
contiver apenas 0 e espaços (se todos os comandos no tubo forem bem-sucedidos).command1 && command2 | command3
se algum desses falhar, sua solução retorna um valor diferente de zero.Infelizmente, a resposta de Johnathan requer arquivos temporários e as respostas de Michel e Imron requerem bash (mesmo que esta pergunta seja shell marcada). Conforme já apontado por outros, não é possível abortar o pipe antes de os processos posteriores serem iniciados. Todos os processos são iniciados de uma vez e, portanto, todos serão executados antes que qualquer erro possa ser comunicado. Mas o título da pergunta também perguntava sobre códigos de erro. Eles podem ser recuperados e investigados após a conclusão do tubo para descobrir se algum dos processos envolvidos falhou.
Aqui está uma solução que detecta todos os erros no tubo e não apenas os erros do último componente. Portanto, é como o pipefail do bash, apenas mais poderoso no sentido de que você pode recuperar todos os códigos de erro.
Para detectar se algo falhou, um
echo
comando é impresso no erro padrão caso algum comando falhe. Em seguida, a saída do erro padrão combinado é salva$res
e investigada posteriormente. É também por isso que o erro padrão de todos os processos é redirecionado para a saída padrão. Você também pode enviar essa saída/dev/null
ou deixá-la como mais um indicador de que algo deu errado. Você pode substituir o último redirecionamento/dev/null
por um arquivo se não precisar armazenar a saída do último comando em qualquer lugar.Para brincar mais com essa construção e se convencer de que ela realmente faz o que deveria, substituí
./a
,./b
e./c
por subshells que executamecho
,cat
eexit
. Você pode usar isso para verificar se essa construção realmente encaminha todas as saídas de um processo para outro e se os códigos de erro são registrados corretamente.fonte