Reutilizando dados de tubulação para diferentes comandos

6

Eu gostaria de usar o mesmo pipe para diferentes aplicativos, como em:

cat my_file | {
  cmd1
  cmd2
  cmd3
}

O Cmd1 deve consumir parte da entrada. O Cmd2 deve consumir outra parte e assim por diante.

No entanto, cada cmd consome mais da entrada, então ele realmente precisa de buffer.

Por exemplo:

yes | nl | { 
  head -n 10 > /dev/null
  cat 
} | head -n 10

Saídas da linha 912 em vez da linha 11.

Tee não é uma boa opção, porque cada comando deve consumir parte do stdin.

Existe uma maneira simples de fazer isso funcionar?

    
por arnaldocan 22.07.2013 / 22:24

2 respostas

3

Você pode usar o comando tee para duplicar o processamento do fluxo completo por meio de muitos comandos:

( ( seq 1 10 | tee /dev/fd/5 | sed s/^/line..\ / >&4 ) 5>&1 | wc -l ) 4>&1 
line.. 1
line.. 2
line.. 3
line.. 4
line.. 5
line.. 6
line.. 7
line.. 8
line.. 9
line.. 10
10

ou dividir linha por linha, usando bash:

while read line ;do
    echo cmd1 $line
    read line && echo cmd2 $line
    read line && echo cmd3 $line
  done < <(seq 1 10)
cmd1 1
cmd2 2
cmd3 3
cmd1 4
cmd2 5
cmd3 6
cmd1 7
cmd2 8
cmd3 9
cmd1 10

Finalmente, há uma maneira de executar cmd1 , cmd2 e cmd3 apenas uma vez com 1/3 do fluxo como STDIN :

( ( ( seq 1 10 |
         tee /dev/fd/5 /dev/fd/6 |
           sed -ne '1{:a;p;N;N;N;s/^.*\n//;ta;}' |
           cmd1 >&4
     ) 5>&1 |
       sed -ne '2{:a;p;N;N;N;s/^.*\n//;ta;}' |
       cmd2 >&4
  ) 6>&1 |
    sed -ne '3{:a;p;N;N;N;s/^.*\n//;ta;}' |
    cmd3 >&4
) 4>&1 
command_1: 1
command_1: 4
command_1: 7
command_1: 10
Command-2: 2
Command-2: 5
Command-2: 8
command 3: 3
command 3: 6
command 3: 9

Para tentar isso, você pode usar:

alias cmd1='sed -e "s/^/command_1: /"' \
    cmd2='sed -e "s/^/Command_2: /"' \
    cmd3='sed -e "s/^/Command_3: /"'

Para usar um fluxo em um processo diferente se estiver no mesmo script, você pode fazer:

(
    for ((i=(RANDOM&7);i--;));do
        read line;
        echo CMD1 $line
      done
    for ((i=RANDOM&7;i--;));do
        read line
        echo CMD2 $line
      done
    while read line ;do
        echo CMD3 $line
      done
)
CMD1 1
CMD1 2
CMD1 3
CMD2 4
CMD2 5
CMD2 6
CMD2 7
CMD2 8
CMD2 9
CMD3 10

Para isso, você pode ter que transformar seus scripts separados em função bash para poder construir um script geral.

Outra forma poderia ser garantir que cada script não produza nada para STDOUT , do que adicionar um cat no final de cada script para poder encadear eles :

#!/bin/sh

for ((i=1;1<n;i++));do
   read line
   pRoCeSS the $line
   echo >output_log
 done

cat

O comando final pode se parecer com:

seq 1 10 | cmd1 | cmd2 | cmd2
    
por 22.07.2013 / 22:27
2

Para que head -n 10 consiga ler 10 linhas e não mais um caractere do canal em stdin, ele teria que ler um caractere por vez para não ler nada depois do último caractere de nova linha. Isso seria ineficiente.

Isso é o que o read shell embutido faz quando stdin não é procurado.

{
  head -n 10 > /dev/null
  cat
} < myfile

Funciona porque head lê um bloco de dados e lseek s volta para o final da décima linha. Isso obviamente não pode ser feito com canos.

Em sistemas GNU ou FreeBSD recentes, alguns aplicativos que usam stdio podem ser instruídos a ler seus caracteres de entrada um de cada vez usando stdbuf -i1 ou stdbuf -i0 .

No entanto, isso não funciona com o GNU head . Funciona com o GNU sed , então você poderia fazer:

seq 20 | {
  stdbuf -i0 sed -n 10q
  cat
}

Como alternativa, o que você pode fazer é controlar o que acontece em um canal para que sempre haja apenas uma linha de cada vez.

Por exemplo, no Linux, você pode fazer:

one_line_at_a_time() {
  perl -MTime::HiRes=usleep -pe '
    BEGIN{$|=1;open F, "<", "/dev/fd/1"; $r=""; vec($r,fileno(F),1) = 1}
    usleep(1000) while select($ro=$r,undef,undef,0)'
}
seq 20 | one_line_at_a_time | { head -n 10 > /dev/null; cat; }

Esse script perl abre "/ dev / fd / 1" no modo de leitura que no Linux faz com que a outra extremidade do canal conectada ao fd 1 (stdout) seja aberta. Dessa forma, com select , ele pode verificar se há algo no tubo (e dormir até esvaziá-lo) antes de enviar a próxima linha.

Claro, isso também é terrivelmente ineficiente.

    
por 22.07.2013 / 23:54

Tags