Desejo executar um comando de longa execução no Bash, e ambos capturam seu status de saída e tee sua saída.
Então eu faço isso:
command | tee out.txt
ST=$?
O problema é que a variável ST captura o status de saída tee
e não do comando. Como posso resolver isso?
Observe que o comando está demorando muito tempo e redirecionar a saída para um arquivo para visualizá-lo mais tarde não é uma boa solução para mim.
bash
shell
error-handling
pipe
flybywire
fonte
fonte
Respostas:
Há uma variável interna do Bash chamada
$PIPESTATUS
; é uma matriz que mantém o status de saída de cada comando em seu último pipeline de comandos em primeiro plano.Ou outra alternativa que também funciona com outros shells (como zsh) seria ativar o pipefail:
A primeira opção não funciona
zsh
devido a uma sintaxe um pouco diferente.fonte
exit ${PIPESTATUS[0]}
.usando bash
set -o pipefail
é útilfonte
( set -o pipefail; command | tee out.txt ); ST=$?
set -o pipefail
executar o comando e, imediatamente depois,set +o pipefail
desmarcar a opção.-o pipefail
ele saberia se o canal falha, mas se 'command' e 'tee' falharem, ele receberia o código de saída de 'tee'.Solução idiota: conectando-os através de um pipe nomeado (mkfifo). Em seguida, o comando pode ser executado em segundo.
fonte
mkfifo
e podem exigir,mknod -p
se bem me lembro.mkfifo
oumknod -p
: no meu caso, o comando adequado para criar o arquivo de pipe eramknod FILE_NAME p
.Há uma matriz que fornece o status de saída de cada comando em um pipe.
fonte
Esta solução funciona sem usar recursos específicos do bash ou arquivos temporários. Bônus: no final, o status de saída é realmente um status de saída e não uma sequência de caracteres em um arquivo.
Situação:
você deseja o status de
someprog
saída e a saída defilter
.Aqui está a minha solução:
Veja minha resposta para a mesma pergunta em unix.stackexchange.com para obter uma explicação detalhada e uma alternativa sem sub-conchas e algumas ressalvas.
fonte
Ao combinar
PIPESTATUS[0]
e o resultado da execução doexit
comando em uma subshell, você pode acessar diretamente o valor de retorno do seu comando inicial:command | tee ; ( exit ${PIPESTATUS[0]} )
Aqui está um exemplo:
Darei à você:
return value: 1
fonte
VALUE=$(might_fail | piping)
que não definirá o PIPESTATUS no shell principal, mas definirá seu nível de erro. Usando:VALUE=$(might_fail | piping; exit ${PIPESTATUS[0]})
Eu quero o que queria.command_might_fail | grep -v "line_pattern_to_exclude" || exit ${PIPESTATUS[0]}
no caso não tee, mas grep filtragemEntão, eu queria contribuir com uma resposta como a da Lesmana, mas acho que a minha talvez seja um pouco mais simples e um pouco mais vantajosa, solução de Bourne-shell pura:
Eu acho que isso é melhor explicado de dentro para fora - command1 executará e imprimirá sua saída regular em stdout (descritor de arquivo 1); depois que estiver pronto, printf executará e imprimirá o código de saída do icommand1 em seu stdout, mas esse stdout será redirecionado para descritor de arquivo 3.
Enquanto o comando1 está em execução, seu stdout está sendo canalizado para o comando2 (a saída do printf nunca chega ao comando2 porque enviamos para o descritor de arquivo 3 em vez de 1, que é o que o canal lê). Em seguida, redirecionamos a saída do command2 para o descritor de arquivo 4, para que ele também fique fora do descritor de arquivo 1 - porque queremos que o descritor de arquivo 1 fique livre por um pouco mais tarde, porque traremos a saída printf do descritor de arquivo 3 de volta para o descritor de arquivo 1 - porque é isso que a substituição de comando (os backticks) captura 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 capturará o que está escrito no padrão da perspectiva dos comandos dentro dele - mas como a saída do command2 vai arquivar o descritor 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 de comando, ele ainda está efetivamente indo para o descritor de arquivo geral 1 do script.(
exec 4>&1
Tem que ser um comando separado, porque muitos shells comuns não gostam quando você tenta gravar em um descritor de arquivo dentro de uma substituição de comando, que é aberto no comando "externo" que está usando a substituição. Portanto, este é o maneira portátil mais simples de fazer isso.)Você pode vê-lo de uma maneira menos técnica e mais divertida, como se as saídas dos comandos estivessem saltando uma contra a outra: command1 canaliza para command2, então a saída do printf salta sobre o comando 2 para que o comando2 não o pegue e depois a saída do comando 2 salta para cima e para fora da substituição de comando, assim como printf chega bem a tempo de ser capturada pela substituição, para que acabe na variável, e a saída do comando2 segue seu caminho alegre sendo gravada na saída padrão, assim como em um tubo normal.
Além disso, pelo que entendi,
$?
ainda conterá o código de retorno do segundo comando no canal, porque atribuições de variáveis, substituições de comandos e comandos compostos são efetivamente transparentes ao código de retorno do comando dentro deles, portanto, o status de retorno de command2 deve ser propagado - isso, e não sendo necessário definir uma função adicional, é por isso que acho que essa pode ser uma solução um pouco melhor do que a proposta por lesmana.De acordo com as advertências mencionadas por lesmana, é possível que o command1 acabe, em algum momento, usando os descritores de arquivo 3 ou 4; portanto, para ser mais robusto, faça o seguinte:
Observe que eu uso comandos compostos no meu exemplo, mas subshells (usar em
( )
vez de{ }
também funcionará, embora talvez seja menos eficiente.)Os comandos herdam os descritores de arquivo do processo que os inicia; portanto, 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, ele4>&-
garante que o comando composto interno não herda o descritor de arquivo quatro e o3>&-
herdeiro não descreva o arquivo três; portanto, o comando1 obtém um ambiente mais limpo e padrão. Você também pode mover o interior4>&-
para o próximo3>&-
, mas acho que não basta limitar o escopo o máximo possível.Não sei com que frequência as coisas usam o descritor de arquivos 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 diretamente no descritor de arquivo 3, palpite (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, é provável que o último seja lembrado e usado em casos de uso geral.
fonte
No Ubuntu e Debian, você pode
apt-get install moreutils
. Ele contém um utilitário chamadomispipe
que retorna o status de saída do primeiro comando no canal.fonte
Diferentemente da resposta do @ cODAR, isso retorna o código de saída original do primeiro comando e não apenas 0 para o sucesso e 127 para a falha. Mas como @Chaoran apontou, você pode simplesmente ligar
${PIPESTATUS[0]}
. É importante, no entanto, que tudo seja colocado entre colchetes.fonte
Fora do bash, você pode fazer:
Isso é útil, por exemplo, em scripts ninja em que o shell deve estar
/bin/sh
.fonte
PIPESTATUS [@] deve ser copiado para uma matriz imediatamente após o retorno do comando pipe. Qualquer leitura do PIPESTATUS [@] apaga o conteúdo. Copie-o para outra matriz se você planeja verificar o status de todos os comandos de canal. "$?" é o mesmo valor que o último elemento de "$ {PIPESTATUS [@]}" e a leitura parece destruir "$ {PIPESTATUS [@]}", mas ainda não o verifiquei totalmente.
Isso não funcionará se o tubo estiver em uma subcasca. Para uma solução para esse problema,
consulte bash pipestatus no comando backticked?
fonte
A maneira mais simples de fazer isso no bash simples é usar a substituição do processo em vez de um pipeline. Existem várias diferenças, mas elas provavelmente não importam muito para o seu caso de uso:
pipefail
opção e aPIPESTATUS
variável são irrelevantes para processar a substituição.Com a substituição do processo, o bash apenas inicia o processo e esquece, nem sequer é visível no
jobs
.Diferenças mencionadas à parte,
consumer < <(producer)
eproducer | consumer
são essencialmente equivalentes.Se você deseja alterar qual é o processo "principal", basta alternar os comandos e a direção da substituição para
producer > >(consumer)
. No seu caso:Exemplo:
Como eu disse, existem diferenças na expressão de pipe. O processo pode nunca parar de funcionar, a menos que seja sensível ao fechamento do tubo. Em particular, ele pode continuar escrevendo coisas no seu stdout, o que pode ser confuso.
fonte
Solução de casca pura:
E agora com o segundo
cat
substituído porfalse
:Observe que o primeiro gato também falha, porque o stdout fica fechado nele. A ordem dos comandos com falha no log está correta neste exemplo, mas não conte com isso.
Esse método permite capturar stdout e stderr para os comandos individuais, para que você possa despejar isso também em um arquivo de log se ocorrer um erro ou apenas excluí-lo se não houver erro (como a saída do dd).
fonte
Baseie-se na resposta de @ brian-s-wilson; esta função auxiliar bash:
usado assim:
1: get_bad_things deve ter êxito, mas não deve produzir saída; mas queremos ver a saída que ela produz
2: todo o pipeline deve ter sucesso
fonte
Às vezes, pode ser mais simples e claro usar um comando externo, em vez de se aprofundar nos detalhes do bash. O pipeline , da linguagem de script de processo mínima execline , sai com o código de retorno do segundo comando *, assim como um
sh
pipeline, massh
, ao contrário , permite reverter a direção do pipe, para que possamos capturar o código de retorno do produtor processo (o abaixo é tudo nash
linha de comando, mas comexecline
instalado):O uso
pipeline
tem as mesmas diferenças para os pipelines de bash nativos que a substituição do processo de bash usada na resposta # 43972501 .* Na verdade
pipeline
, não sai, a menos que haja um erro. Ele é executado no segundo comando, então é o segundo comando que faz o retorno.fonte