Як безпечно поєднувати рядки, надруковані кількома програмами?


11

Припустимо, я хочу паралельно виконувати кілька програм і об'єднати їхні результати в одну трубу:

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недоліків)?


Як ви хочете змішати вихід? Мабуть, ви хочете змішати результат, оскільки ви хочете "порядок сну", а не "порядок аргументів". Чи є ваша вимога змішувати вихідний результат, але не рядки, тобто друкувати кожний рядок атомним шляхом?
Жил "ТАК - перестань бути злим"

Лінійно. Усі рядки з усіх запущених програм повинні бути доставлені достроково, але без змішування всередині кожного рядка.
Ві.

Я думаю, стандартний спосіб зробити це називається, ну, syslog...
Шадур

Використовується syslogне для журналів, а для чогось на замовлення?
Ві.

Це не більш ідеально, ніж інші пропозиції, розміщені до цих пір, але я подумав, що варто згадати -sваріант для хвоста. наприклад tail -f -s .1 file, зменшиться затримка циклу до .1 секунди від 1 секунди за замовчуванням.
cpugeniusmv

Відповіді:


4

GNU Paralellel.

З приміток до релізу від серпня 2013 року:

--line-bufferбуде буферувати вихід на лінійній основі. --groupзберігає вихід на всю роботу. --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

1

Рішення, що реалізує блокування:

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

Слід створити більш швидкий спосіб створення блокування, ніж використання файлової системи.


0

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

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

Наприклад:

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

Не красиво. Навряд чи це надійно. Навряд чи це буде добре.
Ві.

Ти маєш рацію. Це не найкрасивіше, але більше схоже на брудний злом. Однак я не думаю, що цього достатньо, щоб оцінити продуктивність та надійність. Крім того, ви хотіли використовувати "стандартні інструменти". Тож я не здивувався б, якщо вам доведеться погодитися з деякою потворністю (врешті-решт). Але, можливо, хтось має більш задовільне рішення.
xwst

В даний час я задоволений своєю програмою (пов’язаною з питанням), за винятком того, що вона недоступна у сховищах, тому не можна вважати навіть трохи "стандартним". Вирішенням може бути спробу просунути його туди ...
Vi.

0

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

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

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

wait; wait; wait; wait

1
Як це захистить від керування, коли job1і виведе job2довгі (> 4096 байт) рядки? Це, мабуть, названо трубним еквівалентом самого першого коду, про який йдеться.
Ві.

Дуже справедлива точка. Я не розглядав вихід з великим кроком, незважаючи на те, що він чітко закликався у вашому запитанні. Мені зараз цікаво, чи не є, можливо, якийсь інструмент, який робить реверс tee, який звучить як саме те, що ви хочете. Можливо, погляньте на внутрішні syslogабо інші інструменти реєстрації, оскільки вони безумовно об'єднують вихід з декількох місць в один файл журналу. Блокування може бути правильною відповіддю, як @emmanual запропонував також.
DopeGhoti

0

Я знаю, старе питання, але мені було цікаво те саме, і ось що я придумав:

garbling_job | (
    while read LINE
    do
        echo $LINE
    done
) &

Мені здається, що я можу почати досить багато таких, не турбуючись про те, що випали назовні.

Ось моя тестова програма

if [ "$1" = "go" ]
then
for i in 1 2
do
    printf 111112222222222223333
    sleep .01
    printf 3333333444444444444555555555555
    sleep .01
    printf 6666666666666667777
    sleep .01
    printf 777777788888888889999999999999999
    sleep .01
    echo
done
exit
fi

# running them in sequence is all very fine
for i in 1 2 3 4 5 6 7 8
do
    echo bash $0 go 
done

# now this is all garbled up
for i in 1 2 3 4 5 6 7 8
do
    bash $0 go &
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# using cat inbetween does not make it better
for i in 1 2 3 4 5 6 7 8
do
    bash $0 go | cat &
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# it does not help to use stdbuff after the thing that just printfs sporadicall
for i in 1 2 3 4 5 6 7 8
do
    bash $0 go | stdbuf -oL cat &
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# it does not help to use stdbuff before either - or I am not understanding stdbuff
for i in 1 2 3 4 5 6 7 8
do
    stdbuf -o10000 bash $0 go | stdbuf -oL cat &
echo
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# can I read - yes - they are now fine again
for i in 1 2 3 4 5 6 7 8
do
bash $0 go | (
    while read LINE
    do
        echo $LINE
    done
) &
echo
done
for i in 1 2 3 4 5 6 7 8; do wait; done

1
Вам потрібно протестувати рядки, розміри яких перевищують розмір сторінки (як правило, 4-8 К). Дивіться mywiki.wooledge.org/…
Ole Tange
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.