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

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недостатков)?

Vi.
источник
Как вы хотите смешать вывод? Очевидно, вы хотите смешать вывод, так как вы хотите «порядок сна», а не «порядок аргументов». Требуется ли смешивать вывод, а не строки, т.е. чтобы каждая строка печаталась атомарно?
Жиль "ТАК - перестань быть злым"
Построчный. Все строки из всех запущенных программ должны доставляться рано, но без смешивания внутри каждой строки.
Ви.
Я думаю, что стандартный способ сделать это называется, ну, syslog...
Шадур
Использование syslogне для журналов, а для чего-то нестандартного считается нормальным?
Ви.
Это не более идеальный -sвариант, чем другие предложения, опубликованные до сих пор, но я подумал, что стоит упомянуть вариант хвоста. Например tail -f -s .1 file, сократит задержку цикла до 0,1 секунды с 1 секунды по умолчанию.
cpugeniusmv

Ответы:

3

GNU Parallel.

Из примечаний к выпуску от августа 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
cpugeniusmv
источник
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
источник
Не красиво. Вряд ли это надежно. Маловероятно, что производительность будет хорошей.
Ви.
Ты прав. Это не красиво, но больше похоже на грязный хак. Однако я не думаю, что этого достаточно, чтобы судить о производительности и надежности. Кроме того, вы хотели использовать «стандартные инструменты». Так что я бы не удивился, если бы вы приняли какое-то безобразие (в конце концов). Но, возможно, у кого-то есть более удовлетворительное решение.
13:30
В настоящее время я доволен своей программой (ссылка на которую есть в вопросе), за исключением того, что она недоступна в репозиториях, поэтому ее нельзя считать даже «стандартной». Решением может быть попытка протолкнуть его туда ...
Ви.
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
DopeGhoti
источник
1
Как это защитит от искажения job1и job2выдает длинные (> 4096 байт) строки? Похоже, это именованный конвейерный эквивалент самого первого примера кода в Questionion.
Ви.
Очень справедливо. Я не рассматривал вывод большого блога, несмотря на то, что он был явно вызван в вашем вопросе. Теперь мне интересно, нет ли какого-нибудь инструмента, который бы делал наоборот tee, который звучит именно так, как вы хотите. Возможно, посмотрите на внутренние компоненты syslogили другие инструменты ведения журнала, потому что они определенно объединяют вывод из нескольких мест в один файл журнала. Блокировка вполне может быть правильным ответом, как и @emmanual.
DopeGhoti