Как «объединить» линии, напечатанные несколькими программами, безопасно?

Предположим, я хочу параллельно выполнять несколько программ и объединять их выходы в один канал:

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

Этот подход к оболочке хорошо работает для этого простого случая, но я ожидаю, что он потерпит неудачу, если программы выводят все больше и больше строк буферизованным способом, как это (построено):

 qqq qqwww q2 qqq3www2 wwweee3 eee2 eee3 

Одним из решений, которое я намекнул использовать, был 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) 

, но это неоптимальный вариант: он выводит данные вяло, он не заканчивается; Я вижу выходы не в порядке «сна», а в порядке аргументов в этом случае:

 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 

Я реализовал для этого специальную небольшую программу, но считаю, что должен быть какой-то стандартный хороший способ сделать это.

Как это сделать, используя стандартные инструменты (и без недостатка tail -f )?

Параллельно с GNU.

Из примечаний к выпуску от августа 2013 года:

--line-buffer будет буферизовать вывод на линии. --group держит вывод вместе для целой работы. --ungroup позволяет --ungroup с половиной строки, исходящей из одного задания, и половину строки, исходящей из другого задания. --line-buffer устанавливается между этими двумя; он печатает полную строку, но позволяет смешивать строки разных заданий.

Например:

parallel --line-buffer <jobs

Где находятся jobs :

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

Вывод:

 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 

Решение, реализующее блокировки:

 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 

Должен быть более быстрый способ создания блокировки, чем использование файловой системы.

Я не могу придумать ничего простого, что поможет вам, если ваши строки будут такими длинными, что одна программа будет отправлена ​​спать до того, как она сможет, чтобы закончить запись строки в stdout.

Однако, если ваши строки достаточно короткие, чтобы быть полностью написаны до переключения процессов, и ваша проблема заключается в том, что генерация одной строки занимает очень много времени, вы можете буферизовать вывод, используя чтение.

Например:

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

Вы можете сделать именованный канал с mkfifo , выгрузить весь вывод в именованный канал и отдельно прочитать из именованного канала для ваших собранных данных:

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