Comment «combiner» des lignes imprimées par plusieurs programmes en toute sécurité?

11

Supposons que je veuille exécuter plusieurs programmes en parallèle et combiner leurs sorties dans 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'elle échoue si les programmes produisent plus de lignes plus longues de manière tamponnée, comme ceci (construit):

qqq
qqwww
q2
qqq3www2

wwweee3

eee2
eee3

L'une des solutions que j'ai été suggéré d'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 cette option n'est pas optimale: elle produit des données lentement, elle ne se termine pas; Je vois les sorties non 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 pense qu'il devrait y avoir un bon moyen standard de le faire.

Comment le faire en utilisant des outils standards (et sans tail -finconvénient)?

Vi.
la source
Comment voulez-vous mélanger la sortie? Apparemment, vous voulez mélanger la sortie car vous voulez «ordre de sommeil» plutôt que «ordre des arguments». Avez-vous besoin de mélanger la sortie mais pas les lignes, c'est-à-dire que chaque ligne soit imprimée atomiquement?
Gilles 'SO- arrête d'être méchant'
Ligne par ligne. Toutes les lignes de tous les programmes démarrés doivent être livrées tôt, mais sans se mélanger à l'intérieur de chaque ligne.
Vi.
Je pense que la façon standard de faire cela s'appelle, eh bien, syslog...
Shadur
L'utilisation syslogn'est-elle pas pour les journaux, mais pour quelque chose de personnalisé considéré comme OK?
Vi.
Ce n'est pas plus idéal que d'autres suggestions publiées jusqu'à présent, mais j'ai pensé qu'il valait la peine de mentionner l' -soption pour la queue. Par exemple tail -f -s .1 file, le délai de boucle sera réduit à 0,1 seconde par rapport à la valeur par défaut de 1 seconde.
cpugeniusmv

Réponses:

3

GNU Parallel.

D'après les notes de version datées d'août 2013:

--line-buffertamponnera la sortie en ligne. --groupconserve la sortie ensemble pour un travail entier. --ungrouppermet de mélanger la sortie avec une demi-ligne provenant d'un travail et une demi-ligne provenant d'un autre travail. --line-buffers'inscrit 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

jobscontient:

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

Production:

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
la source
1

Une solution mettant en œuvre 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 fichiers.

Emmanuel
la source
0

Je ne peux pas penser à quelque chose de simple, qui vous aidera, si vos lignes sont si longues, qu'un programme sera mis en veille avant d'avoir pu, pour terminer l'écriture d'une ligne sur stdout.

Cependant, si vos lignes sont suffisamment courtes pour être entièrement écrites avant la commutation de processus et que votre problème est que la génération d'une ligne prend très longtemps, vous pouvez mettre en mémoire tampon la sortie à l'aide de la lecture.

Par exemple:

((./script1 | while read line1; do echo $line1; done) & \
(./script2 | while read line2; do echo $line2; done)) | doSomethingWithOutput
xwst
la source
Pas belle. Peu probable que fiable. Peu probable que les performances soient bonnes.
Vi.
Vous avez raison. Ce n'est pas beau mais ressemble plus à un hack sale. Cependant, je ne pense pas que ce soit suffisant pour juger des performances et de la fiabilité. De plus, vous vouliez utiliser des «outils standard». Je ne serais donc pas surpris si vous deviez accepter une certaine laideur (à la fin). Mais peut-être que quelqu'un a une solution plus satisfaisante.
2013
Actuellement, je suis satisfait de mon programme (lié dans la question), sauf qu'il n'est pas disponible dans les référentiels et ne peut donc pas être considéré même un peu "standard". La solution peut être d'essayer de le pousser là ...
Vi.
0

Vous pouvez créer un canal nommé avec mkfifo, vider toute la sortie dans le canal nommé et lire séparément à partir du canal 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
DopeGhoti
la source
1
Comment cela protégera-t-il de la manipulation job1et de la job2sortie de longues lignes (> 4096 octets)? Cela semble être nommé l'équivalent du premier exemple de code en question.
Vi.
Point très juste. Je n'ai pas envisagé la sortie de gros blob malgré le fait qu'elle soit explicitement mentionnée dans votre question. Je me demande maintenant s'il n'y a peut-être pas un outil qui fasse l'inverse de tee, qui ressemble exactement à ce que vous voulez. Regardez peut-être les composants internes syslogou d'autres outils de journalisation, car ils regroupent définitivement la sortie de plusieurs endroits dans un fichier journal. Le verrouillage peut également être la bonne réponse, comme l'a suggéré @emmanual.
DopeGhoti