Comment «combiner» les lignes imprimées par plusieurs programmes en toute security?

Supposons que je veux exécuter plusieurs programmes en parallèle et combiner leurs sorties à un seul canal:

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

Cette approche shell fonctionne bien pour ce cas simple, mais je m'attends à ce qu'il échoue si les programmes produisent plus et plus de lignes de manière tamponnée, comme ceci (construit):

 qqq qqwww q2 qqq3www2 wwweee3 eee2 eee3 

Une des solutions que je voulais utiliser était 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) 

, mais c'est l'option sous-optimale: elle produit des données paresseusement, elle ne se termine pas; Je vois les sorties qui ne sont pas dans l'ordre "sleep", mais dans l'ordre des arguments dans ce cas:

 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 

J'ai mis en place un petit programme spécial pour cela, mais je crois qu'il devrait y avoir un bon moyen standard de le faire.

Comment le faire en utilisant des outils standard (et sans tail -f désavantage)?

GNU Parallel.

Des notes de publication datées d'août 2013:

--line-buffer production en ligne. --group garde la sortie set pour un travail entier. --ungroup permet à la sortie de se mélanger avec une demi-ligne provenant d'un travail et une demi-ligne provenant d'un autre travail. --line-buffer entre ces deux; il imprime une ligne complète, mais permettra de mélanger des lignes de différents travaux.

Par exemple:

parallel --line-buffer <jobs

Où les jobs contient:

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

Sortie:

 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 

Une solution implémentant des verrous:

 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 

Il devrait y avoir un moyen plus rapide de créer un verrou que d'utiliser le système de files.

Je ne peux pas penser à quelque chose de simple, cela vous aidera, si vos lignes sont si longues, qu'un programme sera envoyé dormir avant qu'il ait pu, pour finir d'écrire une ligne à stdout.

Cependant, si vos lignes sont suffisamment courtes pour être écrites entièrement avant le changement de process et que votre problème est que la génération d'une ligne prend très longtime, vous pouvez mettre en memory tampon la sortie en utilisant read.

Par exemple:

 ((./script1 | while read line1; do echo $line1; done) & \ (./script2 | while read line2; do echo $line2; done)) | doSomethingWithOutput 

Vous pouvez créer un tube nommé avec mkfifo , vider toute la sortie dans le tube nommé et lire séparément le tube nommé pour vos données collectées:

 mkfifo /tmp/mypipe job1 > /tmp/mypipe & job2 > /tmp/mypipe & job3 > /tmp/mypipe & cat /tmp/mypipe > /path/to/final_output & wait; wait; wait; wait