Do código bash
command1 | tee >(command2) | command3
Eu quero capturar a saída de command2
in var2
e a saída de command3
in var3
.
command1
é limitado por E/S e os outros comandos são caros, mas podem começar a funcionar antes do command1
término.
A ordem das saídas de command2
e command3
não são fixas. Então eu tentei usar descritores de arquivo em
read -r var2 <<< var3=(command1 | tee >(command2 >&3) | command3) 3>&1
ou
{read -u 3 -r var2; read -r var3} <<< command1 | tee >(command2 >&3) | command3
mas não teve sucesso.
Existe uma maneira de executar os três comandos em paralelo, armazenar os resultados em variáveis diferentes e não criar arquivos temporários?
Então você quer canalizar a saída de cmd1 para cmd2 e cmd3 e obter a saída de cmd2 e cmd3 em diferentes variáveis?
Então parece que você precisa de dois tubos do shell, um conectado à saída do cmd2 e outro à saída do cmd3, e o shell para usar
select()
/poll()
para ler esses dois tubos.bash
não serve para isso, você precisaria de um shell mais avançado comozsh
.zsh
não tem uma interface bruta parapipe()
, mas se estiver no Linux, você pode usar o fato de que/dev/fd/x
em um pipe regular funciona como um pipe nomeado e usar uma abordagem semelhante à usada em Ler/gravar no mesmo descritor de arquivo com redirecionamento de shellSe eu entendi bem todos os seus requisitos, você pode conseguir isso
bash
criando um canal sem nome por comando, redirecionando a saída de cada comando para seu respectivo canal sem nome e, finalmente, recuperando cada saída de seu canal em uma variável separada.Assim, a solução pode ser assim:
Aqui, observe particularmente:
<(:)
construto; este é um truque não documentado do Bash para abrir pipes "sem nome"echo EOF
como forma de notificar oswhile
loops de que não haverá mais saída. Isso é necessário porque não adianta apenas fechar os pipes sem nome (que normalmente terminariam qualquerwhile read
loop) porque esses pipes são bidirecionais, ou seja, usados tanto para escrita quanto para leitura. Não conheço nenhuma maneira de abri-los (ou convertê-los) no par usual de descritores de arquivo, um sendo o final de leitura e o outro o final de gravação.Neste exemplo, usei uma abordagem de bash puro (além do uso de
tee
) para esclarecer melhor o algoritmo básico exigido pelo uso desses pipes sem nome, mas você poderia fazer as duas atribuições com algunssed
no lugar doswhile
loops, como emvar2="$(sed -ne '/^EOF$/q;p' <&${pipe2})"
para variável2 e seu respectivo para variável3, produzindo o mesmo resultado com muito menos digitação. Ou seja, tudo seria:Solução enxuta para pequena quantidade de dados
Para exibir as variáveis de destino, lembre-se de desabilitar a divisão de palavras limpando o IFS, assim:
caso contrário, você perderia novas linhas na saída.
O acima parece uma solução bastante limpa, de fato. Infelizmente, ele só pode funcionar para uma saída não muito grande, e aqui sua milhagem pode variar: em meus testes, encontrei problemas em cerca de 530k de saída. Se você estiver dentro do limite (bem muito conservador) de 4k, você deve estar bem.
A razão para esse limite está no fato de que duas atribuições como essas, ou seja , sintaxe de substituição de comandos , são operações síncronas, o que significa que a segunda atribuição é executada somente após a conclusão da primeira, enquanto pelo contrário
tee
alimenta os dois comandos simultaneamente e bloqueando todos deles se algum deles preencher seu buffer de recebimento. Um impasse.A solução para isso requer um script um pouco mais complexo, para esvaziar os dois buffers simultaneamente. Para este fim, um
while
laço sobre os dois tubos seria útil.Uma solução mais padrão para qualquer quantidade de dados
Um Bashismo mais padrão é como:
Aqui você multiplexa as linhas de ambos os comandos no único descritor de arquivo “stdout” padrão e, em seguida, desmultiplexa a saída mesclada em cada variável respectiva.
Observe particularmente:
sed
comandos para preceder cada linha de saída com as strings "cmd2:" ou "cmd3:" (respectivamente) para o script saber a qual variável cada linha pertencestdbuf -oL
para definir o buffer de linha para a saída dos comandos: isso ocorre porque os dois comandos aqui compartilham o mesmo descritor de arquivo de saída e, como tal, eles substituiriam facilmente a saída um do outro na condição de corrida mais típica se acontecerem de transmitir dados ao mesmo tempo; saída de buffer de linha ajuda a evitar queUma maneira segura de exibir adequadamente esses arrays indexados pode ser assim:
Claro que você também pode usar
"${var2[*]}"
como em:mas isso não é muito eficiente quando há muitas linhas.
Encontrei algo que parece funcionar bem:
Ele funciona definindo um canal anônimo
<(:)
para o descritor de arquivo 3 e canalizando a saídacommand2
para ele.var3
captura a saída decommand3
e a última linha lê do descritor de arquivo 3 até que não receba nenhum dado novo por 0,01 segundo.Ele só funciona para uma saída de até 65536 bytes, dos
command2
quais parecem ser armazenados em buffer pelo pipe anônimo.Eu não gosto da última linha da solução. Prefiro ler tudo de uma vez e não esperar 0,01 segundo, mas parar assim que o buffer estiver vazio. Mas eu não conheço nenhuma maneira melhor.
Isso é possível desde 4.0. bash adicionou uma palavra reservada do shell coproc. Ele bifurca o comando que segue para segundo plano como um subprocesso (que normalmente não permitiria a passagem de variáveis.), porém cria um array (padrão COPROC, mas pode ser nomeado). ${COPROC[0]} se conecta à entrada padrão do subprocesso ${COPROC 1 } sua saída padrão. Esses processos podem ser manipulados como tarefas, são assíncronos, então você pode usar apenas tee para canalizar para os dois coprocessos e ter eles saem para um arquivo separado, cada um deles combinando as saídas no valor de retorno de cada um chamando "${COPROCFIRST 1 } ${COPROCSECOND 1 }", o que é ótimo porque nem precisa estar dentro do pipeline.
bash coproc command_1 { command1 arg1 arg2 arg3 >> command_1_output.txt } coproc command_2 { command2 arg1 arg2 arg3 >> command_2_output.txt } othercommand | tee >^${command_1 1 }" >&"${command_2 1 }" read -r resultados1 <&"${command_1[0]" read -r resultados2 <&"${command_2[0]" echo "$result1 $result2" | command3 >> resultado combinado.txtComo mencionado, esta solução está atualmente incompleta, então estou atacando, no entanto, o princípio é sólido e é semelhante à resposta acima. No entanto, vou direcioná-lo para alguns bons artigos sobre o assunto e retornarei a esta resposta quando o trabalho permitir.
Exemplo de configuração de variáveis em um coprocesso
Análise detalhada do coprocessamento e dos pipes nomeados
Usos e armadilhas