Ответ 1
Хорошо, похоже, что когда команда 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
который этот вопрос связан с 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, вам будет не повезло с этим подходом.
Еще одна хакерская опция - обнулить 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