Obtenha o status de saída do processo que ' s canalizou para outro

Tenho dois processos foo e bar, conectado por um tubo:

$ foo | bar 

bar sempre sai de 0; Estou interessado no código de saída de foo. Existe alguma maneira de chegar lá?

Comentários

Resposta

bash e zsh têm uma variável de matriz que mantém o status de saída de cada elemento (comando) do último pipeline executado pelo shell.

Se você estiver usando bash, a matriz é chamada de PIPESTATUS (caso importa!) e os indicadores da matriz começam em zero:

$ false | true $ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}" 1 0 

Se você estiver usando zsh, a matriz é chamada de pipestatus (caso importa!) e os índices da matriz começam em um:

$ false | true $ echo "${pipestatus[1]} ${pipestatus[2]}" 1 0 

Para combiná-los dentro de uma função de uma maneira que não perca os valores:

$ false | true $ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$? $ echo $retval_bash $retval_zsh $retval_final 1 0 

Execute o acima em bash ou zsh e você “obterá os mesmos resultados; apenas um de retval_bash e retval_zsh será definido. O outro ficará em branco. Isso permitiria que uma função terminasse com return $retval_bash $retval_zsh (observe a falta de aspas!).

Comentários

  • E pipestatus em zsh. Infelizmente, outros shells não ‘ têm esse recurso.
  • Observação: os arrays em zsh começam de forma não intuitiva no índice 1, então ‘ s echo "$pipestatus[1]" "$pipestatus[2]".
  • Você pode verificar todo o pipeline desta forma: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
  • @JanHudec: Talvez você deva ler as primeiras cinco palavras da minha resposta. Também gentilmente indique onde a pergunta solicitou uma resposta apenas POSIX.
  • @JanHudec: Nem foi marcada como POSIX. Por que você acha que a resposta deve ser POSIX? Não foi especificado, portanto, forneci uma resposta qualificada. Não há nada incorreto sobre minha resposta, além de haver várias respostas para tratar de outros casos.

Resposta

Lá são 3 maneiras comuns de fazer isso:

Pipefail

A primeira maneira é definir a opção pipefail (ksh, zsh ou bash). Este é o mais simples e o que ele faz é basicamente definir o status de saída $? para o código de saída do último programa para sair diferente de zero (ou zero se todos saíram com sucesso).

$ false | true; echo $? 0 $ set -o pipefail $ false | true; echo $? 1 

$ PIPESTATUS

O Bash também tem uma variável de matriz chamada $PIPESTATUS ($pipestatus em zsh) que contém o status de saída de todos os programas no último pipeline.

$ true | true; echo "${PIPESTATUS[@]}" 0 0 $ false | true; echo "${PIPESTATUS[@]}" 1 0 $ false | true; echo "${PIPESTATUS[0]}" 1 $ true | false; echo "${PIPESTATUS[@]}" 0 1 

Você pode usar o terceiro exemplo de comando para obter o valor específico de que você precisa no pipeline.

Execuções separadas

Esta é a mais difícil das soluções. Execute cada comando separadamente e capture o status

$ OUTPUT="$(echo foo)" $ STATUS_ECHO="$?" $ printf "%s" "$OUTPUT" | grep -iq "bar" $ STATUS_GREP="$?" $ echo "$STATUS_ECHO $STATUS_GREP" 0 1 

Comentários

  • Droga! Eu ia postar sobre o PIPESTATUS.
  • Para referência, há várias outras técnicas discutidas nesta pergunta do SO: stackoverflow.com/questions/1221833/…
  • @Patrick a solução pipestatus funciona no bash, apenas mais uma pergunta caso eu use o script ksh, você acha que podemos encontrar algo semelhante ao pipestatus? , (enquanto vejo o pipestatus não suportado por ksh)
  • @yael eu não ‘ não uso ksh, mas olhando rapidamente para a página de manual ‘ s, ela não ‘ suporta $PIPESTATUS ou algo semelhante. Ele suporta a opção pipefail.
  • Decidi usar pipefail porque me permite obter o status do comando com falha aqui: LOG=$(failed_command | successful_command)

Resposta

Esta solução funciona sem usar recursos específicos do bash ou arquivos temporários . Bônus: no final, o status de saída é na verdade um status de saída e não uma string em um arquivo.

Situação:

someprog | filter 

você deseja o status de saída de someprog e a saída de filter.

Aqui está minha solução:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 

o resultado desta construção é stdout de filter como stdout da construção e status de saída de someprog como status de saída da construção.


esta construção também funciona com agrupamento de comandos simples {...} em vez de subshells (...). subshells têm algumas implicações, entre outras, um custo de desempenho, que não precisamos aqui. leia o manual do bash fino para obter mais detalhes: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1 

Infelizmente, a gramática bash requer espaços e pontos-e-vírgulas para as chaves, de modo que a construção se torne muito mais espaçosa.

Para o restante deste texto, usarei a variante do subshell.


Exemplo someprog e filter:

someprog() { echo "line1" echo "line2" echo "line3" return 42 } filter() { while read line; do echo "filtered $line" done } ((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 echo $? 

Exemplo de saída:

filtered line1 filtered line2 filtered line3 42 

Observação: o processo filho herda os descritores de arquivo abertos do pai. Isso significa que someprog herdará o descritor de arquivo aberto 3 e 4. Se someprog gravar no descritor de arquivo 3, então isso se tornará o status de saída. O status de saída real será ignorado porque read lê apenas uma vez.

Se você se preocupa que seu someprog possa escrever para o descritor de arquivo 3 ou 4, então é melhor fechar os descritores de arquivo antes de chamar someprog.

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 

O exec 3>&- 4>&- antes de someprog fechar o descritor de arquivo antes de executar someprog para someprog esses descritores de arquivo simplesmente não existem.

Também pode ser escrito assim: someprog 3>&- 4>&-


Explicação passo a passo da construção:

( ( ( ( someprog; #part6 echo $? >&3 #part5 ) | filter >&4 #part4 ) 3>&1 #part3 ) | (read xs; exit $xs) #part2 ) 4>&1 #part1 

De baixo para cima:

  1. Um subshell é criado com o descritor de arquivo 4 redirecionado para stdout. Isso significa que tudo o que for impresso no descritor de arquivo 4 no subshell terminará como o stdout de toda a construção.
  2. Um tubo é criado e os comandos à esquerda (#part3) e à direita (#part2) são executados. exit $xs também é o último comando do pipe e isso significa que a string de stdin será o status de saída de toda a construção.
  3. Um subshell é criado com o arquivo descritor 3 redirecionado para stdout. Isso significa que tudo o que for impresso no descritor de arquivo 3 neste subshell terminará em #part2 e, por sua vez, será o status de saída de toda a construção.
  4. A pipe é criado e os comandos à esquerda (#part5 e #part6) e à direita (filter >&4) são executados. A saída de filter é redirecionada para o descritor de arquivo 4. Em #part1, o descritor de arquivo 4 foi redirecionado para stdout. Isso significa que a saída de filter é o stdout de toda a construção.
  5. O status de saída de #part6 é impresso para o descritor de arquivo 3. Em #part3, o descritor de arquivo 3 foi redirecionado para #part2. Isso significa que o status de saída de #part6 será o status de saída final para toda a construção.
  6. someprog é executado. O status de saída é obtido em #part5. O stdout é obtido pelo tubo em #part4 e encaminhado para filter. A saída de filter, por sua vez, alcançará stdout conforme explicado em #part4

Comentários

  • Legal. Para a função, posso apenas fazer (read; exit $REPLY)
  • (exec 3>&- 4>&-; someprog) simplifica para someprog 3>&- 4>&-.
  • Este método também funciona sem subshells: { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1

Resposta

Embora não seja exatamente o que você perguntou, você pode usar

#!/bin/bash -o pipefail 

para que seus tubos retornem o último retorno diferente de zero.

pode ser um pouco menos codificador

Editar: Exemplo

[root@localhost ~]# false | true [root@localhost ~]# echo $? 0 [root@localhost ~]# set -o pipefail [root@localhost ~]# false | true [root@localhost ~]# echo $? 1 

Comentários

  • set -o pipefail dentro do script deve ser mais robusto, por exemplo caso alguém execute o script via bash foo.sh.
  • Como isso funciona? você tem um exemplo?
  • Observe que -o pipefail não está em POSIX.
  • Isso não funciona no meu BASH 3.2.25 ( 1) -lease. No topo de / tmp / ff, tenho #!/bin/bash -o pipefail.O erro é: /bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
  • @FelipeAlvarez: Alguns ambientes (incluindo linux) don ‘ t analise espaços em #! linhas além da primeira, e isso se torna /bin/bash -o pipefail /tmp/ff, em vez do necessário /bin/bash -o pipefail /tmp/ffgetopt (ou similar) análise usando a optarg, que é o próximo item em ARGV, como o argumento para -o, por isso falha. Se você fosse fazer um wrapper (digamos, bash-pf que acabou de fazer exec /bin/bash -o pipefail "$@" e colocá-lo no #! linha, isso funcionaria. Veja também: en.wikipedia.org/wiki/Shebang_%28Unix%29

Resposta

O que eu faço quando possível é alimentar o código de saída de foo em bar. Por exemplo, se eu sei que foo nunca produz uma linha apenas com dígitos, posso apenas adicionar o código de saída:

{ foo; echo "$?"; } | awk "!/[^0-9]/ {exit($0)} {…}" 

Ou se eu souber que a saída de foo nunca contém uma linha com apenas .:

{ foo; echo .; echo "$?"; } | awk "/^\.$/ {getline; exit($0)} {…}" 

Isso sempre pode ser feito se houver alguma maneira de obter bar para trabalhar em todas menos a última linha e passar a última linha como seu código de saída.

Se bar for um pipeline complexo cuja saída você não é necessário, você pode ignorar parte dela imprimindo o código de saída em um descritor de arquivo diferente.

exit_codes=$({ { foo; echo foo:"$?" >&3; } | { bar >/dev/null; echo bar:"$?" >&3; } } 3>&1) 

Depois disso $exit_codes geralmente é foo:X bar:Y, mas poderia ser bar:Y foo:X se bar sair antes de ler todas as suas entradas ou se você não tiver sorte. Acho que as gravações em tubos de até 512 bytes são atômicas em todos os unices, então as partes foo:$? e bar:$? não serão “misturadas como desde que as strings da tag tenham menos de 507 bytes.

Se você precisar capturar a saída de bar, ficará difícil. Você pode combinar as técnicas acima organizando para a saída de bar nunca conter uma linha que se pareça com uma indicação de código de saída, mas fica complicada.

output=$(echo; { { foo; echo foo:"$?" >&3; } | { bar | sed "s/^/^/"; echo bar:"$?" >&3; } } 3>&1) nl=" " foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*} bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*} output=$(printf %s "$output" | sed -n "s/^\^//p") 

E, claro, há a opção simples de usar um arquivo temporário para armazenar o status. Simples, mas não tão simples em produção:

  • Se houver vários scripts em execução simultaneamente ou se o mesmo script usar este método em vários lugares, você precisa fazer certifique-se de que usam nomes de arquivo temporário diferentes.
  • Criar um arquivo temporário com segurança em um diretório compartilhado é difícil. Freqüentemente, /tmp é o único lugar onde um script com certeza é capaz de gravar arquivos. Use mktemp , que não é POSIX, mas está disponível em todos os unices sérios hoje em dia.
foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file") 

Comentários

  • Ao usar a abordagem de arquivo temporário, prefiro adicionar uma armadilha para EXIT que remove todos os arquivos temporários de modo que nenhum lixo será deixado, mesmo se o script morrer

Resposta

Começando pelo pipeline:

foo | bar | baz 

Aqui está uma solução geral usando apenas o shell POSIX e nenhum arquivo temporário:

exec 4>&1 error_statuses="`((foo || echo "0:$?" >&3) | (bar || echo "1:$?" >&3) | (baz || echo "2:$?" >&3)) 3>&1 >&4`" exec 4>&- 

$error_statuses contém os códigos de status de quaisquer processos com falha, em ordem aleatória, com índices para dizer qual comando emitiu cada status.

# if "bar" failed, output its status: echo "$error_statuses" | grep "1:" | cut -d: -f2 # test if all commands succeeded: test -z "$error_statuses" # test if the last command succeeded: ! echo "$error_statuses" | grep "2:" >/dev/null 

Observe as aspas em torno de $error_statuses em meus testes; sem eles, grep não é possível “diferenciar porque as novas linhas são forçadas para espaços.

Resposta

Se você tiver o pacote moreutils instalado, poderá usar o utilitário mispipe , que faz exatamente o que você pediu.

Resposta

Então, eu queria contribuir com uma resposta como lesmana “s, mas acho que a minha talvez seja um pouco mais simples e um pouco mais vantajosa – Solução Bourne-shell:

# You want to pipe command1 through command2: exec 4>&1 exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1` # $exitstatus now has command1"s exit status. 

Acho que isso é melhor explicado de dentro para fora – o command1 irá executar e imprimir sua saída regular em stdout (descritor de arquivo 1) , então, uma vez feito isso, printf irá executar e imprimir o código de saída command1 em seu stdout, mas esse stdout é redirecionado para o descritor de arquivo 3.

Enquanto command1 está sendo executado, seu stdout está sendo canalizado para command2 (a saída de printf nunca chega ao command2 porque o enviamos para o descritor de arquivo 3 em vez de 1, que i é o que o tubo lê).Em seguida, redirecionamos a saída de command2 para o descritor de arquivo 4, de modo que também fique fora do descritor de arquivo 1 – porque queremos o descritor de arquivo 1 livre um pouco mais tarde, porque traremos a saída de printf no descritor de arquivo 3 de volta para descritor de arquivo 1 – porque é isso que a substituição do comando (os crases) irá capturar e é isso que será colocado na variável.

A parte final da mágica é que primeiro exec 4>&1 fizemos como um comando separado – ele abre o descritor de arquivo 4 como uma cópia do stdout do shell externo. A substituição de comando irá capturar tudo o que está escrito na saída padrão da perspectiva dos comandos dentro dele – mas, uma vez que a saída de command2 vai para o descritor de arquivo 4 no que diz respeito à substituição de comando, a substituição de comando não o captura – no entanto, uma vez que “sai” da substituição do comando, ele efetivamente ainda vai para o descritor de arquivo geral 1. do script.

(O exec 4>&1 tem para ser um comando separado porque muitos shells comuns não gostam quando você tenta escrever para um descritor de arquivo dentro de uma substituição de comando, que é aberto no comando “externo” que está usando a substituição. Portanto, esta é a maneira portátil mais simples para fazê-lo.)

Você pode olhar para isso de uma forma menos técnica e mais lúdica, como se as saídas dos comandos estivessem se superando: o comando1 direciona para o comando2, então a saída do printf pula sobre o comando 2 para que o comando2 não o pegue e, em seguida, a saída do comando 2 salta para cima e para fora do A substituição do comando assim como printf chega bem a tempo de ser capturado pela substituição de modo que termine na variável, e a saída do command2 segue seu caminho alegre sendo escrita na saída padrão, assim como em um pipe normal.

Além disso, pelo que entendi, $? ainda conterá o código de retorno do segundo comando no tubo, porque atribuições de variáveis, substituições de comando e comandos compostos são todos efetivamente transparentes para o código de retorno do comando dentro deles, então o status de retorno do comando2 deve ser propagado – isso, e não tendo que definir uma função adicional, é por que eu acho que pode ser uma solução um pouco melhor do que a proposta por lesmana.

De acordo com as advertências que lesmana menciona, é possível que command1 em algum ponto acabe usando os descritores de arquivo 3 ou 4, então, para ser mais robusto, você faria:

exec 4>&1 exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1` exec 4>&- 

Observe que eu uso comandos compostos em meu exemplo, mas subshells (usando ( ) em vez de { } também funcionará, embora talvez possa ser menos eficiente.)

Os comandos herdam os descritores de arquivo do processo que os inicia, então a segunda linha inteira herdará o descritor de arquivo quatro, e o comando composto seguido por 3>&1 herdará o descritor de arquivo três. Portanto, o 4>&- garante que o comando composto interno não herdará o descritor de arquivo quatro, e o 3>&- não herdará o descritor de arquivo três, portanto command1 obtém um ambiente mais “limpo” e padrão. Você também pode mover o 4>&- interno ao lado de 3>&-, mas eu descobri por que não limitar seu escopo o máximo possível.

Não sei com que frequência as coisas usam o descritor de arquivo três e quatro diretamente – acho que na maioria das vezes os programas usam syscalls que retornam descritores de arquivo não usados no momento, mas às vezes o código grava no arquivo descritor 3 diretamente, eu acho (eu poderia imaginar um programa verificando um descritor de arquivo para ver se ele está aberto, e usando-o se estiver, ou se comportando de maneira diferente se não estiver). Portanto, este último é provavelmente melhor manter em mente e uso para casos de uso geral.

Comentários

  • Parece interessante, mas posso ‘ não consigo descobrir o que você espera que esse comando faça, e meu computador também pode ‘ t; eu obtenho -bash: 3: Bad file descriptor.
  • @ G-Man Certo, sempre esqueço que o bash não tem ideia do que ‘ está fazendo quando está mes para descritores de arquivo, ao contrário dos shells que eu normalmente uso (o ash que vem com o busybox). Eu ‘ avisarei você quando pensar em uma solução alternativa que deixe o bash feliz. Nesse ínterim, se você ‘ tiver um debian box à mão, pode experimentá-lo no painel, ou se ‘ tiver um busybox à mão pode tentar com o busybox ash / sh.
  • @ G-Man Quanto ao que eu espero que o comando faça, e o que ele faz em outros shells, é redirecionar stdout do command1 para que não ‘ não é pego pela substituição de comando, mas uma vez fora da substituição de comando, ele vai deixa fd3 de volta para stdout para ‘ ser canalizado conforme o esperado para command2.Quando o comando1 sai, o printf dispara e imprime seu status de saída, que é capturado na variável pela substituição do comando. Análise muito detalhada aqui: stackoverflow.com/questions/985876/tee-and-exit-status/… Também , aquele seu comentário foi lido como se fosse um insulto?
  • Por onde devo começar? (1) Lamento se você se sentiu insultado. “Parece interessante” significava seriamente; seria ótimo se algo tão compacto funcionasse tão bem quanto você esperava. Além disso, eu estava dizendo, simplesmente, que não entendia o que sua solução deveria estar fazendo. Eu tenho trabalhado / jogado com Unix por um longo tempo (desde antes de o Linux existir), e, se eu não entendo algo, isso é uma bandeira vermelha que, talvez, outras pessoas também não vão entender e que precisa de mais explicações (IMNSHO). … (Cont.)
  • (Cont.)… Desde você “… gosta de pensar… que [você] entende quase tudo mais do que a pessoa média ”, talvez você deva lembrar que o objetivo do Stack Exchange não é ser um serviço de gravação de comandos, produzindo milhares de soluções pontuais para questões trivialmente distintas; mas sim para ensinar às pessoas como resolver seus próprios problemas. E, para esse fim, talvez você precise explicar as coisas bem o suficiente para que uma “pessoa média” possa entendê-las. Veja a resposta de lesmana para um exemplo de explicação excelente. … (Cont.)

Resposta

A solução de lesmana “acima também pode ser feita sem a sobrecarga de iniciar subprocessos aninhados usando { .. } (lembrando que esta forma de comandos agrupados sempre deve terminar com ponto-e-vírgula). Algo assim:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1 

Eu verifiquei esta construção com o traço versão 0.5.5 e bash versões 3.2.25 e 4.2.42, então mesmo que alguns shells não sejam compatíveis com { .. } agrupamento, ainda é compatível com POSIX.

Comentários

  • Isso funciona muito bem com a maioria dos shells I ‘ tentamos com, incluindo NetBSD sh, pdksh, mksh, dash, bash. No entanto, não posso ‘ fazê-lo funcionar com AT & T Ksh (93s +, 93u +) ou zsh (4.3.9, 5.2), mesmo com set -o pipefail em ksh ou qualquer número de wait em qualquer um dos dois. Acho que pode, em parte pelo menos, seja um problema de análise para ksh, como se eu continuasse usando subshells, então funcionaria bem, mas mesmo com um if para escolher a variante de subshell para ksh, mas deixe os comandos compostos para outros, falha.

Resposta

Seguir é um complemento para a resposta de @Patrik, caso você não seja capaz de usar uma das soluções comuns.

Esta resposta assume o seguinte:

  • Você tem um shell que não conhece $PIPESTATUS nem set -o pipefail
  • Você deseja usar um tubo para execução paralela, portanto, nenhum arquivo temporário.
  • Você não quero bagunça adicional se interromper o script, possivelmente por uma queda repentina de energia.
  • Esta solução deve ser relativamente fácil de seguir e limpa para ler.
  • Você deve não deseja introduzir subshells adicionais.
  • Você não pode mexer nos descritores de arquivo existentes, então stdin / out / err não deve ser tocado (como você pode introduzir algum novo temporariamente)

Suposições adicionais. Você pode se livrar de todos, mas isso prejudica demais a receita, por isso não é abordado aqui:

  • Tudo o que você quer saber é que todos os comandos no PIPE têm código de saída 0.
  • Você não precisa de informações adicionais da banda lateral.
  • Seu shell espera que todos os comandos de pipe retornem.

Antes: foo | bar | baz, no entanto, isso retorna apenas o código de saída do último comando (baz)

Desejado: $? não deve ser 0 (verdadeiro), se algum dos comandos no pipe falhou

Depois:

TMPRESULTS="`mktemp`" { rm -f "$TMPRESULTS" { foo || echo $? >&9; } | { bar || echo $? >&9; } | { baz || echo $? >&9; } #wait ! read TMPRESULTS <&8 } 9>>"$TMPRESULTS" 8<"$TMPRESULTS" # $? now is 0 only if all commands had exit code 0 

Explicação:

  • Um arquivo temporário é criado com mktemp. Isso geralmente cria imediatamente um arquivo em /tmp
  • Este arquivo temporário é então redirecionado para FD 9 para gravação e FD 8 para leitura
  • Em seguida, o tempfile é excluído imediatamente. Ele permanece aberto, entretanto, até que ambos os FDs deixem de existir.
  • Agora o pipe foi iniciado. Cada etapa adiciona ao FD 9 apenas, se houver um erro.
  • O wait é necessário para ksh, porque ksh senão não espera que todos os comandos de pipe terminem. No entanto, observe que há efeitos colaterais indesejados se algumas tarefas em segundo plano estiverem presentes, então eu comentei por padrão.Se a espera não doer, você pode comentar.
  • Depois o conteúdo do arquivo é lido. Se estiver vazio (porque tudo funcionou) read retorna false, então true indica um erro

Isso pode ser usado como um substituto do plug-in para um único comando e só precisa do seguinte:

  • FDs 9 e 8 não usados
  • Uma única variável de ambiente para conter o nome do arquivo temporário
  • E isso a receita pode ser adaptada a qualquer shell que permita o redirecionamento IO
  • Além disso, é bastante independente de plataforma e não precisa de coisas como /proc/fd/N

BUGs:

Este script tem um bug no caso de /tmp ficar sem espaço. Se você também precisa de proteção contra este caso artificial, você pode fazer isso da seguinte forma, no entanto, isso tem a desvantagem de que o número de 0 em 000 depende do número de comandos nopipe, por isso é um pouco mais complicado:

TMPRESULTS="`mktemp`" { rm -f "$TMPRESULTS" { foo; printf "%1s" "$?" >&9; } | { bar; printf "%1s" "$?" >&9; } | { baz; printf "%1s" "$?" >&9; } #wait read TMPRESULTS <&8 [ 000 = "$TMPRESULTS" ] } 9>>"$TMPRESULTS" 8<"$TMPRESULTS" 

Notas de portabilidade:

  • ksh e shells semelhantes que aguardam apenas o último comando de pipe precisam do wait não comentado

  • O último exemplo usa printf "%1s" "$?" em vez de echo -n "$?" porque é mais portátil. Nem toda plataforma interpreta -n corretamente.

  • printf "$?" faria isso também, entretanto, printf "%1s" captura alguns casos esquivos no caso de você executar o script em alguma plataforma realmente quebrada. (Leia: se acontecer de você programar em paranoia_mode=extreme.)

  • FD 8 e FD 9 podem ser superiores em plataformas que suportam múltiplos dígitos. AFAIR, um shell compatível com POSIX, precisa apenas suportar um dígito.

  • Foi testado com Debian 8.2 sh, bash, ksh, ash, sash e até csh

Resposta

Isso é portátil, ou seja funciona com qualquer shell compatível com POSIX, não requer que o diretório atual seja gravável e permite que vários scripts usando o mesmo truque sejam executados simultaneamente.

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$)) 

Editar: aqui está uma versão mais forte após os comentários de Gilles:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s)) 

Edit2: e aqui está uma variante um pouco mais leve após o comentário duvidoso de Jim:

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)) 

Comentários

  • Isso não ‘ não funciona por vários motivos. 1. O arquivo temporário pode ser lido antes de ‘ ser escrito. 2. Criar um arquivo temporário em um diretório compartilhado com um nome previsível é inseguro (DoS trivial, corrida de link simbólico). 3. Se o mesmo script usar esse truque várias vezes, ‘ sempre usará o mesmo nome de arquivo. Para resolver 1, leia o arquivo após a conclusão do pipeline. Para resolver 2 e 3, use um arquivo temporário com um nome gerado aleatoriamente ou em um diretório privado.
  • +1 Bem, o $ {PIPESTATUS [0]} é mais fácil, mas a ideia básica aqui funciona se um sabe sobre os problemas que Gilles menciona.
  • Você pode salvar alguns subshells: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: concordo que ‘ é mais fácil com o Bash, mas em alguns contextos, vale a pena saber como evitar o Bash.

Resposta

Com um pouco de precaução, isso deve funcionar:

foo-status=$(mktemp -t) (foo; echo $? >$foo-status) | bar foo_status=$(cat $foo-status) 

Comentários

  • Que tal fazer uma limpeza como jlliagre? Não ‘ você não deixa um arquivo chamado foo-status?
  • @Johan: Se você preferir minha sugestão, não ‘ não hesite em votar;) Além de não deixar um arquivo, tem a vantagem de permitir que vários processos sejam executados simultaneamente e o diretório atual não precisa ser gravável.

Resposta

O seguinte bloco “if” será executado somente se “command” for bem-sucedido:

if command; then # ... fi 

Especificamente falando, você pode executar algo assim:

haconf_out=/path/to/some/temporary/file if haconf -makerw > "$haconf_out" 2>&1; then grep -iq "Cluster already writable" "$haconf_out" # ... fi 

Que executará haconf -makerw e armazene seu stdout e stderr em “$ haconf_out”. Se o valor retornado por haconf for verdadeiro, o bloco “if” será executado e grep lerá “$ haconf_out”, tentando para compará-lo com o “Cluster já gravável”.

Observe que os tubos se auto-limpam automaticamente; com o redirecionamento você “terá que ter cuidado para remover” $ haconf_out “quando terminar.

Não tão elegante quanto pipefail, mas uma alternativa legítima se esta funcionalidade não está ao alcance.

Resposta

Alternate example for @lesmana solution, possibly simplified. Provides logging to file if desired. ===== $ cat z.sh TEE="cat" #TEE="tee z.log" #TEE="tee -a z.log" exec 8>&- 9>&- { { { { #BEGIN - add code below this line and before #END ./zz.sh echo ${?} 1>&8 # use exactly 1x prior to #END #END } 2>&1 | ${TEE} 1>&9 } 8>&1 } | exit $(read; printf "${REPLY}") } 9>&1 exit ${?} $ cat zz.sh echo "my script code..." exit 42 $ ./z.sh; echo "status=${?}" my script code... status=42 $ 

Resposta

(Com bash, pelo menos) combinado com set -e pode-se usar subshell para emular explicitamente o pipefail e sair no erro do pipe

set -e foo | bar ( exit ${PIPESTATUS[0]} ) rest of program 

Portanto, se foo falhar por algum motivo – o resto do programa não será executado e o script será encerrado com o código de erro correspondente. (Isso pressupõe que foo imprime seu próprio erro, que é suficiente para entender o motivo da falha)

Resposta

EDITAR : Esta resposta está errada, mas interessante, então deixarei para o futuro referência.


Adicionar um ! ao comando inverte o código de retorno.

http://tldp.org/LDP/abs/html/exit-status.html

# =========================================================== # # Preceding a _pipe_ with ! inverts the exit status returned. ls | bogus_command # bash: bogus_command: command not found echo $? # 127 ! ls | bogus_command # bash: bogus_command: command not found echo $? # 0 # Note that the ! does not change the execution of the pipe. # Only the exit status changes. # =========================================================== # 

Comentários

  • Acho que isso não está relacionado. Em seu exemplo, quero saber o código de saída de ls, não inverter o código de saída de bogus_command
  • Eu sugiro retirar essa resposta.
  • Bem, parece que eu ‘ sou um idiota. Eu ‘ Eu realmente usei isso em um script antes de pensar que fazia o que o OP queria. Ainda bem que não ‘ usei para nada importante

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *