Reproduzido (e melhorado) da comp.unix.shell FAQ (desde que eu aconteça ter escrito essa seção do FAQ):
Como obtenho o código de saída de cmd1 em cmd1 | cmd2
Primeiro, observe que o código de saída cmd1 pode ser diferente de zero e ainda não significa um erro. Isso acontece, por exemplo, em
cmd | head -n 1
você pode observar um status de saída 141 (ou 269 com ksh93 ou 397 com yash) de cmd
,
mas é porque cmd
foi interrompido por um sinal SIGPIPE quando
head -n 1
terminou depois de ler uma linha.
Para saber o status de saída dos elementos de um pipeline
cmd1 | cmd2 | cmd3
com zsh:
Os códigos de saída são fornecidos na matriz pipestatus
special.
O código de saída cmd1
está em $pipestatus[1]
, cmd3
código de saída em
$pipestatus[3]
, de modo que $?
seja sempre o mesmo que
$pipestatus[-1]
.
com bash:
Os códigos de saída são fornecidos na matriz PIPESTATUS
special.
O código de saída cmd1
está em ${PIPESTATUS[0]}
, cmd3
código de saída em
${PIPESTATUS[2]}
, de modo que $?
seja sempre o mesmo que
${PIPESTATUS[-1]}
(ou ${PIPESTATUS[@]: -1}
para versões anteriores a 4.2).
com qualquer outro Bourne como shells
Você precisa usar um truque para passar os códigos de saída para o shell principal. Você pode fazer
usando um tubo (2). Em vez de executar cmd1
, você executa cmd1; echo "$?"
e faz
certeza $? faz o seu caminho para o shell.
exec 3>&1
code='
# now, inside the backticks, fd4 goes to the pipe
# whose other end is read and stored in $code for
# later evaluation; fd1 is the normal standard output
# preserved the line before with exec 3>&1
exec 4>&1 >&3 3>&-
{
cmd1 4>&-; echo "ec1=$?;" >&4
} | {
cmd2 4>&-; echo "ec2=$?;" >&4
} | cmd3 4>&-
echo "ec3=$?;" >&4
'
exec 3>&-
eval "$code"
Saia dos códigos em $ec1
, $ec2
, $ec3
.
com um shell POSIX
Você pode usar essa função para facilitar:
run() {
j=1
while eval "\${pipestatus_$j+:} false"; do
unset "pipestatus_$j"
j=$(($j+1))
done
j=1 com= k=1 l=
for arg do
case $arg in
('|')
com="$com {
$l "'3>&-
echo "pipestatus_'$j'=$?" >&3
} 4>&- |'
j=$(($j+1)) l=;;
(*)
l="$l \"\${$k}\""
esac
k=$(($k+1))
done
com="$com $l"' 3>&- >&4 4>&-
echo "pipestatus_'$j'=$?"'
{ eval "$(exec 3>&1; eval "$com")"; } 4>&1
j=1
ret=0
while eval "\${pipestatus_$j+:} false"; do
eval '[ "$pipestatus_'"$j"'" -eq 0 ] || ret=$pipestatus_'"$j"
j=$(($j+1))
done
return "$ret"
}
Use como:
run cmd1 \| cmd2 \| cmd3
códigos de saída estão em $pipestatus_1
, $pipestatus_2
, $pipestatus_3
e $?
é o status de saída diferente de zero (como na opção pipefail
de alguns shells).