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 em um único pipe:

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

Essa abordagem de shell funciona bem para esse caso simples, mas espero que falhe se os programas produzirem linhas cada vez mais longas no modo buffer, como este (construído):

qqq
qqwww
q2
qqq3www2

wwweee3

eee2
eee3

Uma das soluções que 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 é uma opção abaixo do ideal: gera dados com lentidão, não termina; Eu vejo as saídas não na ordem "sleep", mas na ordem dos 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 implementei um pequeno programa especial para isso, mas acredito que deve haver alguma maneira padrão de fazê-lo.

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

Vi.
fonte
Como você deseja misturar a saída? Aparentemente, você deseja misturar a saída, pois deseja "ordem de suspensão" em vez de "ordem de argumentos". Sua exigência é misturar a saída, mas não as linhas, ou seja, imprimir cada linha atomicamente?
Gilles 'SO- stop be evil'
Linewise. Todas as linhas de todos os programas iniciados devem ser entregues com antecedência, mas sem misturar dentro de cada linha.
Vi.
Acho que a maneira padrão de fazer isso é chamado, bem, syslog...
Shadur
O uso syslognão é para logs, mas para algo personalizado considerado OK?
Vi.
Isso não é mais ideal do que outras sugestões postadas até agora, mas achei que valeria a pena mencionar a -sopção de cauda. por exemplo tail -f -s .1 file, reduzirá o atraso do loop para 0,1 segundos do padrão de 1 segundo.
cpugeniusmv

Respostas:

3

Paralelo GNU.

Das notas de versão de agosto de 2013:

--line-bufferarmazenará em buffer a saída online. --groupmantém a saída unida para um trabalho inteiro. --ungrouppermite que a saída se misture com meia linha proveniente de um trabalho e meia linha proveniente de outro trabalho. --line-bufferencaixa entre esses dois; imprime uma linha completa, mas permite misturar linhas de trabalhos diferentes.

Por exemplo:

parallel --line-buffer <jobs

Onde jobsconté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

Resultado:

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
cpugeniusmv
fonte
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.

Emmanuel
fonte
0

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

No entanto, se suas linhas forem curtas o suficiente para serem gravadas inteiramente antes da alternância do processo, e seu problema é que a geração de uma linha leva muito tempo, você poderá armazenar em buffer a saída usando a leitura.

Por exemplo:

((./script1 | while read line1; do echo $line1; done) & \
(./script2 | while read line2; do echo $line2; done)) | doSomethingWithOutput
xwst
fonte
Não é bonito. Improvável que confiável. Improvável que o desempenho seja bom.
Vi.
Você está certo. Não é bonito, mas parece mais um hack sujo. No entanto, não acho que isso seja suficiente para julgar o desempenho e a confiabilidade. Além disso, você queria usar 'ferramentas padrão'. Então, eu não ficaria surpreso, se você tiver que aceitar alguma feiúra (no final). Mas talvez alguém tenha uma solução mais satisfatória.
Xwst
Atualmente, estou satisfeito com o meu programa (vinculado na pergunta), exceto que ele não está disponível nos repositórios, portanto, não pode ser considerado nem um pouco "padrão". A solução pode ser tentar empurrá-lo para lá ...
Vi.
0

Você pode criar um pipe nomeado mkfifo, despejar toda a saída no pipe nomeado e ler separadamente o pipe 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
DopeGhoti
fonte
1
Como isso protege contra a distorção quando job1e a job2saída de linhas longas (> 4096 bytes)? Isso parece ter o nome de pipe equivalente ao primeiro exemplo de código em questão.
Vi.
Ponto muito justo. Não considerei a saída de blob grande, apesar de ter sido explicitamente mencionada na sua pergunta. Agora estou me perguntando se talvez não exista alguma ferramenta que faça o contrário tee, que soa exatamente como você deseja. Possivelmente, observe as syslogpartes internas de ou outras ferramentas de registro, porque elas definitivamente agregam a saída de vários locais em um arquivo de registro. O bloqueio também pode ser a resposta certa, como o @emmanual sugeriu.
DopeGhoti