Come "combinare" in modo sicuro le linee stampate da più programmi?


11

Supponiamo che io voglia eseguire più programmi in parallelo e combinare i loro output su una pipe:

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

Questo approccio alla shell funziona bene per questo semplice caso, ma mi aspetto che fallisca se i programmi producono più e più linee in modo bufferizzato, come questo (costruito):

qqq
qqwww
q2
qqq3www2

wwweee3

eee2
eee3

Una delle soluzioni che mi è stato suggerito di usare è stata 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)

, ma questa è un'opzione non ottimale: genera dati in modo lento, non termina; Vedo le uscite non in ordine di "sospensione", ma in ordine di argomenti in questo 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

Ho implementato un piccolo programma speciale per questo, ma credo che ci dovrebbe essere un buon modo standard per farlo.

Come farlo utilizzando strumenti standard (e senza tail -fsvantaggi)?


Come si desidera mescolare l'output? Apparentemente si desidera mescolare l'output poiché si desidera "ordine di sospensione" anziché "ordine degli argomenti". Il tuo requisito è quello di mescolare l'output ma non le righe, ovvero avere ciascuna riga stampata atomicamente?
Gilles 'SO- smetti di essere malvagio' il

Formato da linee. Tutte le linee di tutti i programmi avviati devono essere consegnate in anticipo, ma senza mescolarle all'interno di ciascuna linea.
Vi.

Penso che il modo standard di farlo sia chiamato, beh, syslog...
Shadur,

L'uso syslognon è per i registri, ma per qualcosa di personalizzato considerato OK?
Vi.

Questo non è più ideale di altri suggerimenti pubblicati finora, ma ho pensato che varrebbe la pena menzionare l' -sopzione per la coda. ad es. tail -f -s .1 fileridurrà il ritardo del loop a .1 secondi dal 1 secondo predefinito.
cpugeniusmv,

Risposte:


3

GNU Parallel.

Dalle note di rilascio datate agosto 2013:

--line-bufferbufferizzerà l'output sulla base della linea. --groupmantiene l'output insieme per un intero lavoro. --ungroupconsente il mixup dell'output con mezza riga proveniente da un lavoro e mezza riga proveniente da un altro lavoro. --line-buffersi inserisce tra questi due; stampa una linea intera, ma consentirà di mescolare linee di diversi lavori.

Per esempio:

parallel --line-buffer <jobs

Dove jobscontiene:

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

Produzione:

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

1

Una soluzione che implementa i blocchi:

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

Dovrebbe esserci un modo più veloce per creare un blocco rispetto all'utilizzo del filesystem.


0

Non riesco a pensare a niente di semplice, che ti aiuterà, se le tue linee sono così lunghe, che un programma verrà inviato a dormire prima che fosse in grado, di finire di scrivere una linea su stdout.

Tuttavia, se le righe sono abbastanza corte da essere scritte interamente prima della commutazione del processo e il problema è che la generazione di una riga richiede molto tempo, è possibile bufferizzare l'output utilizzando read.

Per esempio:

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

Non bellissimo. Improbabile che affidabile. È improbabile che le prestazioni siano buone.
Vi.

Hai ragione. Non è bello ma sembra più un trucco sporco. Tuttavia, non credo sia abbastanza per giudicare le prestazioni e l'affidabilità. Inoltre, volevi usare gli "strumenti standard". Quindi non sarei sorpreso se dovessi accettare un po 'di bruttezza (alla fine). Ma forse qualcuno ha una soluzione più soddisfacente.
xwst

Attualmente sono soddisfatto del mio programma (collegato alla domanda), tranne per il fatto che non è disponibile nei repository, quindi non può essere considerato nemmeno un po '"standard". La soluzione potrebbe essere quella di provare a spingerlo lì ...
Vi.

0

È possibile creare una pipa denominata con mkfifo, scaricare tutto l'output nella pipa denominata e leggere separatamente dalla pipa denominata per i dati raccolti:

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

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

wait; wait; wait; wait

1
In che modo questo proteggerà dalla distruzione job1e quando emetterà job2linee lunghe (> 4096 byte)? Questo sembra essere chiamato equivalente della pipe del primo esempio di codice in quesion.
Vi.

Punto molto giusto. Non ho preso in considerazione l'output di BLOB di grandi dimensioni nonostante sia stato esplicitamente richiamato nella tua domanda. Ora mi chiedo se forse non esiste uno strumento che fa il contrario tee, che suona esattamente come quello che vuoi. È possibile esaminare gli interni syslogo altri strumenti di registrazione, perché aggregano sicuramente l'output da più posizioni in un unico file di registro. Il blocco potrebbe anche essere la risposta giusta, come suggerito anche da @emmanual.
DopeGhoti,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.