¿Cómo "combinar" líneas impresas por múltiples programas de forma segura?

11

Supongamos que quiero ejecutar varios programas en paralelo y combinar sus salidas en una tubería:

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

Este enfoque de shell funciona bien para este caso simple, pero espero que falle si los programas generan más y más líneas de forma amortiguada, como este (construido):

qqq
qqwww
q2
qqq3www2

wwweee3

eee2
eee3

Una de las soluciones que se me sugirió usar fue 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)

, pero esta es una opción subóptima: emite datos lentamente, no termina; Veo salidas no en orden de "suspensión", sino en orden de argumentos en este 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

He implementado un pequeño programa especial para esto, pero creo que debería haber alguna buena forma estándar de hacerlo.

¿Cómo hacerlo usando herramientas estándar (y sin tail -fdesventajas)?

Vi.
fuente
¿Cómo quieres mezclar la salida? Aparentemente desea mezclar la salida ya que quiere "orden de suspensión" en lugar de "orden de argumentos". ¿Es su requisito mezclar la salida pero no las líneas, es decir, que cada línea se imprima atómicamente?
Gilles 'SO- deja de ser malvado'
Linewise Todas las líneas de todos los programas iniciados deben entregarse antes, pero sin mezclarse dentro de cada línea.
Vi.
Creo que la forma estándar de hacer esto se llama, bueno, syslog...
Shadur
¿Se está utilizando syslogno para registros, sino para algo personalizado considerado correcto?
Vi.
Esto no es más ideal que otras sugerencias publicadas hasta ahora, pero pensé que valdría la pena mencionar la -sopción de cola. por ejemplo tail -f -s .1 file, reducirá el retraso del bucle a .1 segundos desde el valor predeterminado de 1 segundo.
cpugeniusmv

Respuestas:

3

GNU Paralelo

De las notas de la versión de agosto de 2013:

--line-bufferalmacenará en búfer la salida en línea --groupmantiene la salida unida para todo un trabajo. --ungrouppermite que la salida se mezcle con media línea proveniente de un trabajo y media línea proveniente de otro trabajo. --line-bufferencaja entre estos dos; imprime una línea completa, pero permitirá mezclar líneas de diferentes trabajos.

Por ejemplo:

parallel --line-buffer <jobs

Donde 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

Salida:

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
fuente
1

Una solución que implementa bloqueos:

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

Debería haber una forma más rápida de crear un bloqueo que usar el sistema de archivos.

Emmanuel
fuente
0

No puedo pensar en nada simple, que te ayudará, si tus líneas son tan largas, que un programa se enviará a dormir antes de que pueda, para terminar de escribir una línea en stdout.

Sin embargo, si sus líneas son lo suficientemente cortas como para escribirlas completamente antes del cambio de proceso, y su problema es que generar una línea lleva mucho tiempo, puede almacenar la salida en buffer usando la lectura.

P.ej:

((./script1 | while read line1; do echo $line1; done) & \
(./script2 | while read line2; do echo $line2; done)) | doSomethingWithOutput
xwst
fuente
No es hermoso. Improbable que confiable. Es poco probable que el rendimiento sea bueno.
Vi.
Tienes razón. No es hermoso, pero se parece más a un truco sucio. Sin embargo, no creo que sea suficiente para juzgar el rendimiento y la fiabilidad. Además, quería utilizar 'herramientas estándar'. Así que no me sorprendería si tienes que aceptar algo de fealdad (al final). Pero tal vez alguien tenga una solución más satisfactoria.
xwst
Actualmente estoy satisfecho con mi programa (vinculado en la pregunta), excepto que no está disponible en repositorios, por lo que no puede considerarse ni siquiera un poco "estándar". La solución puede ser tratar de llevarlo allí ...
Vi.
0

Puede hacer una tubería con nombre mkfifo, volcar toda la salida en la tubería con nombre y leer por separado de la tubería con nombre para sus datos recopilados:

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

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

wait; wait; wait; wait
DopeGhoti
fuente
1
¿Cómo protegerá esto de la destrucción cuando job1y job2genere líneas largas (> 4096 bytes)? Esto parece llamarse equivalente de canalización del primer ejemplo de código en la consulta.
Vi.
Muy buen punto. No consideré la salida de blob grande a pesar de que se mencionara explícitamente en su pregunta. Ahora me pregunto si quizás no hay alguna herramienta que haga lo contrario tee, que suena exactamente como lo que quieres. Posiblemente observe las syslogpartes internas u otras herramientas de registro, porque definitivamente agregan la salida de varios lugares en un archivo de registro. El bloqueo también puede ser la respuesta correcta, como sugirió @emmanual, también.
DopeGhoti