Возможное состояние гонки с передачей по конвейеру вывода от нескольких получателей tee, поступающих не по порядку в именованный канал в сценарии BASH.

ОБНОВЛЕНИЕ: Хотя на самом деле я не решил первоначальную проблему, связанную с моими усилиями по прокладке труб, я решил свою проблему, значительно упростив ее и просто полностью отказавшись от труб. Вот сценарий проверки концепции, который генерирует параллельно при чтении только один раз с диска контрольные суммы CRC32, MD5, SHA1, SHA224, SHA256, SHA384 и SHA512 и возвращает их как объект JSON (будет использовать вывод в PHP и Руби). Это грубо без проверки ошибок, но работает:

#!/bin/bash

checksums="`tee <"$1" \
        >( cfv -C -q -t sfv -f - - | tail -n 1 | sed -e 's/^.* \([a-fA-F0-9]\{8\}\)$/"crc32":"\1"/' ) \
        >( md5sum - | sed -e 's/^\([a-fA-F0-9]\{32\}\) .*$/"md5":"\1"/' ) \
        >( sha1sum - | sed -e 's/^\([a-fA-F0-9]\{40\}\) .*$/"sha1":"\1"/' ) \
        >( sha224sum - | sed -e 's/^\([a-fA-F0-9]\{56\}\) .*$/"sha224":"\1"/' ) \
        >( sha256sum - | sed -e 's/^\([a-fA-F0-9]\{64\}\) .*$/"sha256":"\1"/' ) \
        >( sha384sum - | sed -e 's/^\([a-fA-F0-9]\{96\}\) .*$/"sha384":"\1"/' ) \
        >( sha512sum - | sed -e 's/^\([a-fA-F0-9]\{128\}\) .*$/"sha512":"\1"/') \
        >/dev/null`\ 
"

json="{"

for checksum in $checksums; do json="$json$checksum,"; done

echo "${json:0: -1}}"

ИСХОДНЫЙ ВОПРОС:

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

Итак, чтобы продолжить, тем не менее, я делаю простой сценарий, позволяющий мне одновременно создавать контрольные суммы CRC32, MD5 и SHA1 для файла, считывая его с диска только один раз. Я использую cfv для этой цели.

Первоначально я просто взломал простой скрипт, который записал файл в тройник с тремя командами cfv, записывающими в три отдельных файла в каталоге /tmp/, а затем попытался вывести их на стандартный вывод после этого, но в итоге получил пустой вывод, если только Я заставил свой скрипт спать на секунду, прежде чем пытаться прочитать файлы. Думая, что это странно, я предположил, что был идиотом в своих сценариях, поэтому я попытался использовать другой подход, заставив вместо этого рабочие процессы cfv выводить данные в именованный канал. Пока что это мой сценарий после применения методов из вышеупомянутой ссылки:

!/bin/bash

# Bail out if argument isn't a file:
[ ! -f "$1" ] && echo "'$1' is not a file!" && exit 1

# Choose a name for a pipe to stuff with CFV output:
pipe="/tmp/pipe.chksms"

# Don't leave an orphaned pipe on exiting or being terminated:
trap "rm -f $pipe; exit" EXIT TERM

# Create the pipe (except if it already exists (e.g. SIGKILL'ed b4)):
[ -p "$pipe" ] || mkfifo $pipe

# Start a background process that reads from the pipe and echoes what it
# receives to stdout (notice the pipe is attached last, at done):
while true; do
        while read line; do
                [ "$line" = "EOP" ] && echo "quitting now" && exit 0
                echo "$line"
        done
done <$pipe 3>$pipe & # This 3> business is to make sure there's always
                      # at least one producer attached to the pipe (the
                      # consumer loop itself) until we're done.

# This sort of works without "hacks", but tail errors out when the pipe is
# killed, naturally, and script seems to "hang" until I press enter after,
# which I believe is actually EOF to tail, so it's no solution anyway:
#tail -f $pipe &

tee <"$1" >( cfv -C -t sfv -f - - >$pipe ) >( cfv -C -t sha1 -f - - >$pipe ) >( cfv -C -t md5 -f - - >$pipe ) >/dev/null

#sleep 1s
echo "EOP" >$pipe
exit

Итак, выполненный в том виде, в котором он есть, я получаю этот вывод:

daniel@lnxsrv:~/tisso$ ./multisfv file
 :  :  : quitting now
- : Broken pipe (CF)
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
- : Broken pipe (CF)
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
- : Broken pipe (CF)
daniel@lnxsrv:~/tisso$ close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

Но, закомментировав спящие 1, я получаю ожидаемый результат,

daniel@lnxsrv:~/tisso$ ./multisfv file
3bc1b5ff125e03fb35491e7d67014a3e *
-: 1 files, 1 OK.  0.013 seconds, 79311.7K/s
5e3bb0e3ec410a8d8e14fef1a6daababfc48c7ce *
-: 1 files, 1 OK.  0.016 seconds, 62455.0K/s
; Generated by cfv v1.18.3 on 2012-03-09 at 23:45.23
;
2a0feb38
-: 1 files, 1 OK.  0.051 seconds, 20012.9K/s
quitting now

Это озадачивает меня, так как я предполагаю, что tee не завершится до тех пор, пока каждый получатель cfv не разветвит данные, и, таким образом, оператор echo "EOP" будет выполняться до тех пор, пока не закончатся все подпотоки cfv, что означало бы, что они записали свой вывод в мой именованный канал... И тогда будет выполнен оператор эха.

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

Любые идеи?

ТИА, Дэниел :)


person DanielSmedegaardBuus    schedule 09.03.2012    source источник
comment
Я ожидаю, что для этих контрольных сумм доступен исходный код. Как насчет того, чтобы объединить их в 1 программу и записать 3 значения, которые вы обрабатываете, в соответствующий файл контрольной суммы. Я должен поверить, что в Perl, вероятно, есть модули для этого, которые, опять же, вы могли бы собрать вместе, чтобы просто выполнить 1 проход по файлу. (Просто нестандартные мысли об этом, YRMV). Удачи!   -  person shellter    schedule 10.03.2012
comment
Поможет ли это? parallel --group 'cfv -C -t sfv -f {} - ;cfv -C -t sha1 -f {} - ;cfv -C -t md5 -f {} - ;' ::: file   -  person potong    schedule 10.03.2012
comment
@shelter - я думаю, что написание собственных подпрограмм всегда является моим запасным вариантом, но я бы предпочел использовать уже доступные инструменты, насколько это возможно.   -  person DanielSmedegaardBuus    schedule 10.03.2012
comment
@potong — параллельная команда, которую предлагает мне Ubuntu — из пакета moreutils — не принимает эту команду. На его справочной странице не упоминается какой-либо аргумент --group? Кроме того, читая его справочную страницу, похоже, что она не нацелена на решение моей проблемы. Кажется, что его цель состоит в том, чтобы распределить команды в подоболочках для грубой балансировки ЦП, и даже если бы я использовал его, каждая подоболочка читала бы из исходного файла, чего я хочу избежать.   -  person DanielSmedegaardBuus    schedule 10.03.2012
comment
Я имел в виду параллель GNU. Но если не поможет... не поможет.   -  person potong    schedule 10.03.2012
comment
@potong - я только что установил параллельную версию GNU, и хотя эта, похоже, все еще не может разветвить один входной поток для всех рабочих, это чертовски хороший маленький швейцарский армейский нож инструмента! Так рад, что вы открыли это для меня! Спасибо за это :)   -  person DanielSmedegaardBuus    schedule 10.03.2012


Ответы (1)


tee выйдет, как только он запишет последний бит ввода в последний выходной канал и закроет его (то есть безымянные каналы, созданные bash, а не ваш fifo, также известный как «именованный канал»). Нет необходимости ждать завершения процессов, читающих каналы; на самом деле, он даже не подозревает, что пишет в каналы. Поскольку каналы имеют буферы, вполне вероятно, что tee закончит запись до того, как процессы на другом конце закончат чтение. Таким образом, сценарий запишет «EOP» в FIFO, что приведет к завершению цикла чтения. Это закроет единственный считыватель fifo, и все процессы cfv получат SIGPIPE, когда они в следующий раз попытаются записать в стандартный вывод.

Здесь возникает очевидный вопрос: почему бы вам просто не запустить три (или N) независимых процессов, читающих файл и вычисляющих разные сводки. Если бы «файл» на самом деле генерировался «на лету» или загружался с какого-то удаленного сайта, или каким-то другим медленным процессом, возможно, имело бы смысл делать что-то так, как вы пытаетесь это сделать, но если файл присутствует на локальном disk, вполне вероятно, что на самом деле произойдет только один доступ к диску; отстающие суммировщики будут считывать файл из буферного кеша. Если это все, что вам нужно, GNU parallel должен работать нормально, или вы можете просто запустить процессы в bash (с помощью &), а затем дождаться их. YMMV, но я думаю, что любое из этих решений будет менее ресурсоемким, чем настройка всех этих каналов и имитация буферного кеша в пользовательской среде с помощью tee.

Кстати, если вы хотите сериализовать вывод из нескольких процессов, вы можете использовать утилиту flock. Просто использовать fifo недостаточно; нет никакой гарантии, что процессы, записывающие в fifo, будут записывать целые строки атомарно, и если бы вы знали, что они это сделали, вам бы не понадобился fifo.

person rici    schedule 13.09.2012