Como “combinar” linhas impressas por vários programas com segurança?

11

Suponha que eu queira executar vários programas em paralelo e combinar suas saídas para um canal:

sh -c '
    (echo qqq; echo qqq2; echo qqq3)&
    (echo www; echo www2; echo www3)& 
    (echo eee; echo eee2; echo eee3)& 
  wait; wait; wait'

Esta abordagem shell funciona bem para este caso simples, mas espero que ele falhe se os programas gerarem mais e mais linhas de maneira bufferizada, como esta (construída):

qqq
qqwww
q2
qqq3www2

wwweee3

eee2
eee3

Uma das soluções que eu sugeri usar foi tail -f :

tail -n +0 -q -f <(echo qqq; echo qqq2; echo qqq3) <(echo www; echo www2; echo www3) <(echo eee; echo eee2; echo eee3)

, mas esta é a opção sub-ótima: ela produz dados com lentidão, não termina; Eu vejo saídas não em ordem "sleep", mas em ordem de argumentos, neste caso:

tail -n +0 -q -f <(sleep 1; echo qqq; sleep 1; echo qqq2; echo qqq3) <(echo www; echo www2; sleep 10; echo www3) <(echo eee; sleep 4; echo eee2; echo eee3) | cat

Eu implementou um pequeno programa especial para isso, mas acredito que deveria haver uma boa maneira padrão de fazer isso.

Como fazer isso usando ferramentas padrão (e sem tail -f desvantagem)?

    
por Vi. 27.11.2013 / 23:26

4 respostas

3

Paralelo GNU.

De notas de lançamento de agosto de 2013:

--line-buffer will buffer output on line basis. --group keeps the output together for a whole job. --ungroup allows output to mixup with half a line coming from one job and half a line coming from another job. --line-buffer fits between these two; it prints a full line, but will allow for mixing lines of different jobs.

Por exemplo:

parallel --line-buffer <jobs

Onde jobs contém:

./long.sh
./short.sh one
./short.sh two

short.sh :

#!/bin/bash

while true; do
        echo "short line $1"
        sleep .1
done

long.sh :

#!/bin/bash

count=0
while true; do
        echo -n "long line with multiple write()s "
        sleep .1
        count=$((count+1))
        if [ $count -gt 30 ]; then
                count=0
                echo
        fi
done

Saída:

short line one
short line two
short line one
short line two
short line one
**-snip-**
short line one
short line one
short line two
short line two
short line one
short line one
short line one
long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s 
short line two
short line two
short line two
short line one
    
por 03.05.2014 / 20:06
1

Uma solução implementando bloqueios:

function putlines () {
   read line || return $?
   while ! ln -s $$ lock >/dev/null 2>&1
   do
      sleep 0.05
   done
   echo "$line" 
}

function getlines () {
     while read lline
     do 
          echo "$lline"
          rm lock
     done
}

# your paralelized jobs  
(  
   job1 | putlines & 
   job2 | putlines & 
   job3 | putlines & 
   wait
) | getlines| final_processing

Deve haver uma maneira mais rápida de criar um bloqueio do que usar o sistema de arquivos.

    
por 20.12.2013 / 19:23
0

Não consigo pensar em nada simples, que irá ajudá-lo, se as suas linhas forem tão longas, que um programa será enviado para dormir antes de poder, para terminar de escrever uma linha para stdout.

No entanto, se suas linhas são curtas o suficiente para serem escritas inteiramente antes de mudar de processo, e seu problema é que gerar uma linha leva muito tempo, você pode armazenar em buffer a saída usando leitura.

Por exemplo:

((./script1 | while read line1; do echo $line1; done) & \
(./script2 | while read line2; do echo $line2; done)) | doSomethingWithOutput
    
por 29.11.2013 / 15:06
0

Você pode criar um canal nomeado com mkfifo , despejar toda a saída no canal nomeado e ler separadamente do canal nomeado para os dados coletados:

mkfifo /tmp/mypipe
job1 > /tmp/mypipe &
job2 > /tmp/mypipe &
job3 > /tmp/mypipe &

cat /tmp/mypipe > /path/to/final_output &

wait; wait; wait; wait
    
por 20.12.2013 / 19:44