AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 507061
Accepted
katosh
katosh
Asked: 2019-03-19 12:57:40 +0800 CST2019-03-19 12:57:40 +0800 CST 2019-03-19 12:57:40 +0800 CST

tee em diferentes variáveis

  • 772

Do código bash

command1 | tee >(command2) | command3

Eu quero capturar a saída de command2in var2e a saída de command3in var3.

command1é limitado por E/S e os outros comandos são caros, mas podem começar a funcionar antes do command1término.

A ordem das saídas de command2e command3nã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?

bash variable
  • 4 4 respostas
  • 2387 Views

4 respostas

  • Voted
  1. Stéphane Chazelas
    2019-03-19T14:08:13+08:002019-03-19T14:08:13+08:00

    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.

    bashnão serve para isso, você precisaria de um shell mais avançado como zsh. zshnão tem uma interface bruta para pipe(), mas se estiver no Linux, você pode usar o fato de que /dev/fd/xem 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 shell

    #! /bin/zsh -
    
    cmd1() seq 20
    cmd2() sed 's/1/<&>/g'
    cmd3() tr 0-9 A-J
    
    zmodload zsh/zselect
    zmodload zsh/system
    typeset -A done out
    {
      cmd1 > >(cmd2 >&3 3>&-) > >(cmd3 >&5 5>&-) 3>&- 5>&- &
      exec 4< /dev/fd/3 6< /dev/fd/5 3>&- 5>&-
      while ((! (done[4] && done[6]))) && zselect -A ready 4 6; do
        for fd (${(k)ready[(R)*r*]}) {
          sysread -i $fd && out[$fd]+=$REPLY || done[$fd]=1
        }
      done
    } 3> >(:) 5> >(:)
    
    printf '%s output: <%s>\n' cmd2 "$out[4]" cmd3 "$out[6]"
    
    • 4
  2. Best Answer
    LL3
    2019-03-20T18:20:15+08:002019-03-20T18:20:15+08:00

    Se eu entendi bem todos os seus requisitos, você pode conseguir isso bashcriando 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:

    : {pipe2}<> <(:)
    : {pipe3}<> <(:)
    
    command1 | tee >({ command2 ; echo EOF ; } >&${pipe2}) >({ command3 ; echo EOF ; } >&${pipe3}) > /dev/null &
    var2=$(while read -ru ${pipe2} line ; do [ "${line}" = EOF ] && break ; echo "${line}" ; done)
    var3=$(while read -ru ${pipe3} line ; do [ "${line}" = EOF ] && break ; echo "${line}" ; done)
    
    exec {pipe2}<&- {pipe3}<&-
    

    Aqui, observe particularmente:

    • o uso do <(:)construto; este é um truque não documentado do Bash para abrir pipes "sem nome"
    • o uso de um simples echo EOFcomo forma de notificar os whileloops de que não haverá mais saída. Isso é necessário porque não adianta apenas fechar os pipes sem nome (que normalmente terminariam qualquer while readloop) 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 alguns sedno lugar dos whileloops, como em var2="$(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

    : {pipe2}<> <(:)
    : {pipe3}<> <(:)
    
    command1 | tee >({ command2 ; echo EOF ; } >&${pipe2}) >({ command3 ; echo EOF ; } >&${pipe3}) > /dev/null &
    var2="$(sed -ne '/^EOF$/q;p' <&${pipe2})"
    var3="$(sed -ne '/^EOF$/q;p' <&${pipe3})"
    
    exec {pipe2}<&- {pipe3}<&-
    

    Para exibir as variáveis ​​de destino, lembre-se de desabilitar a divisão de palavras limpando o IFS, assim:

    IFS=
    echo "${var2}"
    echo "${var3}"
    

    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 teealimenta 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 whilelaço sobre os dois tubos seria útil.

    Uma solução mais padrão para qualquer quantidade de dados

    Um Bashismo mais padrão é como:

    declare -a var2 var3
    while read -r line ; do
       case "${line}" in
       cmd2:*) var2+=("${line#cmd2:}") ;;
       cmd3:*) var3+=("${line#cmd3:}") ;;
       esac
    done < <(
       command1 | tee >(command2 | stdbuf -oL sed -re 's/^/cmd2:/') >(command3 | stdbuf -oL sed -re 's/^/cmd3:/') > /dev/null
    )
    

    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:

    • o uso de arrays indexados como variáveis ​​de destino: isso ocorre porque apenas anexar a uma variável normal se torna terrivelmente lento na presença de muita saída
    • o uso de sedcomandos para preceder cada linha de saída com as strings "cmd2:" ou "cmd3:" (respectivamente) para o script saber a qual variável cada linha pertence
    • o uso necessário de stdbuf -oLpara 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 que
    • note também que tal uso de stdbuf só é necessário para o último comando de cada cadeia, ou seja, aquele que sai diretamente para o descritor de arquivo compartilhado, que neste caso são os comandos sed que precedem a saída de cada comandoX com seu prefixo distinto

    Uma maneira segura de exibir adequadamente esses arrays indexados pode ser assim:

    for ((i = 0; i < ${#var2[*]}; i++)) ; do
       echo "${var2[$i]}"
    done
    

    Claro que você também pode usar "${var2[*]}"como em:

    echo "${var2[*]}"
    

    mas isso não é muito eficiente quando há muitas linhas.

    • 2
  3. katosh
    2019-03-21T02:18:19+08:002019-03-21T02:18:19+08:00

    Encontrei algo que parece funcionar bem:

    exec 3<> <(:)
    var3=$(command1 | tee >(command2 >&3) | command3)
    var2=$(while IFS= read -t .01 -r -u 3 line; do printf '%s\n' "$line"; done)
    

    Ele funciona definindo um canal anônimo <(:)para o descritor de arquivo 3 e canalizando a saída command2para ele. var3captura a saída de command3e 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 command2quais 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.

    • 0
  4. rayiik
    2022-03-24T15:03:51+08:002022-03-24T15:03:51+08:00

    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.txt

    Como 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

    • 0

relate perguntas

  • Problema estranho ao passar variáveis ​​do arquivo de texto

  • Enquanto a linha lê mantendo os espaços de escape?

  • Como salvar um caminho com ~ em uma variável?

  • ordem de substituição de processos `te` e `bash`

  • Execute um script muito lento até que seja bem-sucedido

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve