Então eu queria contribuir com uma resposta como a do lesmana, mas acho que a minha talvez seja uma solução um pouco mais simples e um pouco mais vantajosa de 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.
Eu acho que isso é melhor explicado de dentro para fora - command1 executará e imprimirá sua saída regular em stdout (descritor de arquivo 1), então uma vez feito, printf irá executar e imprimir o código de saída do comando1 em seu stdout, mas stdout é redirecionado para o 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 o faz para o comando2 porque o enviamos para o descritor de arquivo 3 em vez de 1, que é o que o pipe lê). Em seguida, redirecionamos a saída do comando2 para o descritor de arquivo 4, para que ele fique fora do descritor de arquivo 1 - porque queremos que o descritor de arquivo 1 seja livre um pouco mais tarde, porque traremos a saída printf do descritor de arquivo 3 para descritor de arquivo 1 - porque é isso que a substituição do comando (os backticks) irá capturar e é isso que será colocado na variável.
O bit final de mágica é o primeiro exec 4>&1
que fizemos como um comando separado - ele abre o descritor de arquivos 4 como uma cópia do stdout do shell externo. A substituição de comandos irá capturar o que estiver escrito no padrão fora da perspectiva dos comandos dentro dele - mas, como a saída do comando2 vai arquivar o descritor 4 no que diz respeito à substituição de comando, a substituição de comando não o captura. uma vez que ele "saia" da substituição do comando, ele ainda estará efetivamente indo para o descritor de arquivo geral do script 1.
(O exec 4>&1
precisa 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 é aberta no comando "external" que está usando o comando Portanto, esta é a maneira mais simples de fazer isso.)
Você pode olhar para isso de uma maneira menos técnica e mais divertida, como se as saídas dos comandos estivessem saltando umas nas outras: command1 pipes para command2, então a saída do printf salta sobre o comando 2 para que o comando2 não o capture e, em seguida, a saída do comando 2 salta para cima e para fora da substituição do comando, assim como o printf chega na hora de ser capturado pela substituição, de modo que ele fique na variável, e a saída do comando2 é 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 pipe, porque as atribuições de variáveis, as substituições de comandos e os comandos compostos são 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 ter que definir uma função adicional, é por isso que eu acho que essa pode ser uma solução um pouco melhor do que a proposta pela lesmana.
De acordo com as ressalvas que o lesmana menciona, é possível que o comando1 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>&-
Note que eu uso comandos compostos no meu exemplo, mas subshells (usando ( )
ao invés de { }
também funcionarão, embora talvez seja menos eficiente.)
Os comandos herdam os descritores de arquivos do processo que os inicia, portanto, a segunda linha inteira herdará o descritor de arquivos quatro e o comando composto seguido por 3>&1
herdará o descritor de arquivo três. Portanto, o 4>&-
certifica-se de 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, o comando1 obtém um ambiente mais limpo e mais padrão. Você também pode mover o 4>&-
interno próximo ao 3>&-
, mas eu entendo 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 arquivos não usados no momento, mas às vezes escrevem códigos no descritor de arquivos 3 diretamente, eu acho (eu poderia imaginar um programa verificando um descritor de arquivo para ver se ele está aberto, e usá-lo se estiver, ou se comportando de maneira diferente se não estiver). Então, o último é provavelmente o melhor para se manter em mente e usar para casos gerais.