Status de saída do bash usado com o PIPE

10

Estou tentando entender como o status de saída é comunicado quando um pipe é usado. Suponha que eu esteja usando whichpara localizar um programa inexistente:

which lss
echo $?
1

Como whichnão foi possível localizar lss, obtive um status de saída 1. Isso é bom. No entanto, quando tento o seguinte:

which lss | echo $?
0

Isso indica que o último comando executado saiu normalmente. A única maneira de entender isso é que talvez o PIPE também produza um status de saída. Esta é a maneira correta de entender?

sshekhar1980
fonte
2
Todos os componentes de um pipeline são executados ao mesmo tempo . Assim, em which lss | echo $?, o echoé iniciado antes de whichsair; o shell que está gerando a linha de comando echonão tem como saber qual será o status de saída whichposteriormente.
Charles Duffy
Eles não correm ao mesmo tempo. Todo o objetivo do canal é redirecionar a saída de um comando para o outro. O último comando que o shell executou (ou seja, o último em sua instrução) será o que retornará um código de retorno.
Tim S.
3
@TimS. Mas eles são executados ao mesmo tempo, é assim que você pode obter os primeiros bits de saída antes que o primeiro comando seja concluído, se for um comando de execução muito longa.
Random832
Acho que estava pensando no início da execução - há sobreposição definitiva, mas os processos não começam juntos. Entendo o que você quer dizer, meu erro. (O canal está redirecionando a saída, não a execução ...) Eu estraguei meus comentários, mas espero que você entenda o que quero dizer. :)
Tim S.
sim, mas o início da execução não importa, o importante é o fim; o último comando inicia antes que o primeiro termine #
user371366

Respostas:

14

O status de saída do pipeline é o status de saída do último comando no pipeline (a menos que a opção shell pipefailesteja configurada nos shells que o suportam, nesse caso, o status de saída será o do último comando no pipeline que sai com um status diferente de zero).

Não é possível gerar o status de saída de um pipeline de dentro de um pipeline, já que não há como saber qual seria esse valor até que o pipeline tenha realmente terminado de executar.

O gasoduto

which lss | echo $?

é absurdo, pois echonão lê sua entrada padrão (os pipelines são usados ​​para passar dados entre a saída de um comando para a entrada do próximo). O echotambém não iria imprimir o status de saída do gasoduto, mas o status de saída do comando executado imediatamente antes do pipeline.

$ false
$ which hello | echo $?
1
which: hello: Command not found.

$ true
$ which hello | echo $?
0
which: hello: Command not found.

Este é um exemplo melhor:

$ echo hello | read a
$ echo $?
0

$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1

Isso significa que um pipeline também pode ser usado com if:

if gzip -dc file.gz | grep -q 'something'; then
  echo 'something was found'
fi
Kusalananda
fonte
10

O status de saída de um tubo é o status de saída do comando à direita. O status de saída do comando esquerdo é ignorado.

(Observe que which lss | echo $?não mostra isso. Você executaria which lss | true; echo $?para mostrar isso. Em which lss | echo $?, echo $?relata o status do último comando antes deste pipeline.)

A razão pela qual os shells se comportam dessa maneira é que existe um cenário bastante comum em que um erro no lado esquerdo deve ser ignorado. Se o lado direito sair (ou mais geralmente fechar sua entrada padrão) enquanto o lado esquerdo ainda estiver gravando, o lado esquerdo receberá um sinal SIGPIPE. Nesse caso, geralmente não há nada errado: o lado direito não se importa com os dados; se o papel do lado esquerdo é apenas produzir esses dados, não há problema em parar.

No entanto, se o lado esquerdo morrer por algum motivo que não seja o SIGPIPE, ou se o trabalho do lado esquerdo não for apenas produzir dados na saída padrão, um erro no lado esquerdo é um erro genuíno que deve ser relatado.

No sh simples, a única solução é usar um pipe nomeado.

set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1

No ksh, bash e zsh, você pode instruir o shell a fazer com que um pipeline saia com um status diferente de zero se algum componente do pipeline sair com um status diferente de zero. Você precisa definir a pipefailopção:

  • ksh: set -o pipefail
  • bater: shopt -s pipefail
  • zsh: setopt pipefail(ou setopt pipe_fail)

No mksh, bash e zsh, você pode obter o status de cada componente do pipeline usando a variável PIPESTATUS(bash, mksh) ou pipestatus(zsh), que é uma matriz que contém o status de todos os comandos no último pipeline (uma generalização de $?)

Gilles 'SO- parar de ser mau'
fonte
Obrigado Gilles, eu não estava ciente dessas complexidades envolvidas. Isso realmente esclareceu isso ainda mais para mim.
sshekhar1980
4

Sim, o PIPE produz um status de saída; se você quiser o código de saída do comando antes do PIPE, poderá usar$PIPESTATUS

De acordo com as Perguntas e respostas sobre o estouro de pilha , para imprimir o status de saída de um comando ao usar um pipe, você pode:

your_command | echo $PIPESTATUS

Ou defina pipefail com o comando set -o pipefail(para desativá-lo, faça set +o pipefail) e use em $?vez de $PIPESTATUS.

your_command | echo $?

Esses comandos só funcionam depois que você os executa pelo menos uma vez; isso significa que PIPESTATUSsó funciona quando você o executa após o comando com o pipe, assim:

command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

Você receberá 10 por ${PIPESTATUS[0]}e 1 por ${PIPESTATUS[1]}. Consulte estas perguntas e respostas sobre U&L para obter mais informações.

jeremy.Snidaro
fonte
2
Embora PIPESTATUScontenha os status de saída dos comandos no último pipeline, seu primeiro exemplo não faz mais sentido do que o somecmd | echo $?que Kusalananda tratou em sua resposta. false; true | echo $PIPESTATUSimprime 1para o status de saída de false.
Ilkkachu
Upvote para PIPESTATUS e pipefiail, mas os |exhoque realmente está confundindo
Eckes
0

O shell avalia a linha de comando antes da execução do pipeline. Assim $?é o valor do último comando executado antes do pipeline. Se echoele é iniciado antes ou depois whiché irrelevante.

Agora, se sua pergunta foi especificamente sobre a propagação do status de saída, você pode estudar o comportamento padrão como este:

% false | true
% echo $?
0

% true | false
% echo $?
1

Como você pode ver, o status de saída de um pipeline é o status de saída de seu último comando.

alexis
fonte