2015-12-17 7 views
12

Используя подстановку процесса bash, я хочу одновременно запускать две разные команды в файле. В этом примере это не обязательно, но представьте, что «cat/usr/share/dict/words» была очень дорогой операцией, такой как распаковка файла размером 50 гб.Неправильный результат с заменой bash и хвостом?

cat /usr/share/dict/words | tee >(head -1 > h.txt) >(tail -1 > t.txt) > /dev/null 

После этой команды я ожидал бы h.txt содержать первую строку слов файла «A», и t.txt содержать последнюю строку файла «Zyzzogeton».

Однако на самом деле происходит то, что h.txt содержит «A», но t.txt содержит «argillaceo», который составляет около 5% в файле.

Почему это происходит? Кажется, что либо «хвостовой» процесс заканчивается раньше, либо потоки смешиваются.

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

cat /usr/share/dict/words | tee >(grep ^a > a.txt) >(grep ^z > z.txt) > /dev/null 

После этой команды я бы ожидать a.txt содержать все слова, которые начинаются с «а», в то время как z.txt содержит все слова, начинающиеся с «z», что и произошло.

Так почему же это не работает с «хвостом» и с какими другими командами это не работает?

+1

Я думаю, что это связано с http://stackoverflow.com/questions/4489139/bash-process-substitution-and-syncing, который предполагает, что процессы с в выводе подстановки заканчиваются, как только внешняя команда заканчивается, но, честно говоря, я не могу продемонстрировать, что это текущая проблема с любыми командами I «Пробовал до сих пор –

ответ

10

Хорошо, что, кажется, случается, что после того, как команда head -1 заканчивает он выходит и что вызывает tee получить SIGPIPE он пытается писать в именованный канал, что установка замена процесс, который генерирует EPIPE и в соответствии с man 2 write также сгенерируйте SIGPIPE в процессе записи, что приведет к выходу tee, и это приведет к немедленному выходу tail -1, а слева cat также получит SIGPIPE.

Мы можем увидеть это немного лучше, если мы добавим немного больше к процессу с head и сделать вывод как более предсказуемыми, а также записывается в stderr, не полагаясь на tee:

for i in {1..30}; do echo "$i"; echo "$i" >&2; sleep 1; done | tee >(head -1 > h.txt; echo "Head done") >(tail -1 > t.txt) >/dev/null 

, который, когда я бежать он дал мне выход:

1 
Head done 
2 

так получил только еще 1 итерации цикла, прежде чем все выходили (хотя t.txt до сих пор имеет только 1 в этом). Если мы тогда сделали

echo "${PIPESTATUS[@]}" 

мы видим

141 141 

, которые this question связей с SIGPIPE в очень похожим образом на то, что мы видим здесь.

Составители coreutils добавили это в качестве примера к их tee "gotchas" для будущего потомства.

Для обсуждения с разработчиками о том, как это вписывается в соответствие POSIX вы можете увидеть (закрытое notabug) доклад на http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22195

Если у вас есть доступ к GNU версии 8.24 они добавили некоторые опции (не в POSIX), которые могут помочь, например, -p или --output-error=warn. Без этого вы можете взять немного риска, но получить желаемую функциональность в этом вопросе путем захвата и игнорируя SIGPIPE:

trap '' PIPE 
for i in {1..30}; do echo "$i"; echo "$i" >&2; sleep 1; done | tee >(head -1 > h.txt; echo "Head done") >(tail -1 > t.txt) >/dev/null 
trap - PIPE 

будет иметь ожидаемые результаты в обоих h.txt и t.txt, но если что-то еще случилось, что разыскиваемый SIGPIPE для правильной обработки вам будет не повезло с этим подходом.

Другой Hacky вариантом будет обнулить t.txt перед запуском, то не позволяйте head процесса списка закончить до тех пор, пока не нулевой длины:

> t.txt; for i in {1..10}; do echo "$i"; echo "$i" >&2; sleep 1; done | tee >(head -1 > h.txt; echo "Head done"; while [ ! -s t.txt ]; do sleep 1; done) >(tail -1 > t.txt; date) >/dev/null 
+1

Поведенное POSIX поведение для' tee' заключается в том, чтобы он продолжал работать, даже если один из его читателей вышел, поэтому, если вы видите что-то противоположное, на самом деле это ошибка. –

+0

«Если запись в любой успешно открытый файловый операнд завершается с ошибкой, записи в другие успешно открытые файловые операнды и стандартный вывод должны продолжаться, но статус выхода должен быть отличным от нуля. В противном случае применяются действия по умолчанию, указанные в стандартном описании утилиты». - http://pubs.opengroup.org/onlinepubs/9699919799/utilities/tee.html –

+0

@CharlesDuffy хорошо приведенные выше результаты для более старой версии, я думаю, 8.5, я могу попробовать еще раз. Кроме того, я недостаточно разбираюсь в глубине, чтобы узнать, представляет ли замещение процесса как дескриптор закрытого файла или действительно ли он вызывает SIGPIPE, когда этот процесс заканчивается, у меня будет больше работы, прежде чем отправлять отчет об ошибке. Я думаю, –