
Приведенный ниже сценарий не имеет иной цели, кроме как проиллюстрировать этот вопрос.
#!/usr/bin/env zsh
arbitrary_pipeline () {
shuf | tr a-z A-Z
}
tmpdir=$( mktemp -d )
mkfifo $tmpdir/{orig,alt}
{ tee $tmpdir/orig | arbitrary_pipeline > $tmpdir/alt; } &
pid=$!
paste $tmpdir/orig $tmpdir/alt
rm -rf $tmpdir
wait $pid
Скрипт использует tee
и два именованных канала для разделения некоторого (произвольного) стандартного ввода на два потока, перенаправляет один из них в произвольный конвейер и передает полученные два потока входных данных в paste
. Схематично:
STDIN --- > --.- arbitrary_pipeline -.
\ \
paste `----<FIRST-ARGUMENT> `- <SECOND-ARGUMENT> --> STDOUT
(В этом случае arbitrary_pipeline
просто перемешивается его стандартный ввод и преобразуется в верхний регистр, но, как следует из названия, это может быть что угодно.)
Вывод скрипта на stdout выглядит нормально, но wait
команда всегда завершается ошибкой:
% grep -iP 'z.*s.*h' /usr/share/dict/words | /tmp/test.sh
Nietzsche CITIZENSHIP'S
Zubeneschamali NIETZSCHE
Zubeneschamali's ZUBENESCHAMALI
citizenship CITIZENSHIP
citizenship's ZUBENESCHAMALI'S
/tmp/test.sh:wait:18: pid 26357 is not a child of this shell
Что я делаю не так?
Кстати:
/usr/bin/env zsh --version
# zsh 5.0.7 (x86_64-pc-linux-gnu)
ПРАВКИ:
- добавил фигурные скобки вокруг
tee
трубопровода, согласно предложению jordanm. (Хотя результаты не изменились.) - заменено
&!
на&
в ответ на комментарий Стефана Шазеласа. (Опять же, результаты не изменились.)
решение1
До 5.0.8 zsh
не мог ждать уже мертвых заданий. Это было изменено в 5.0.8 в 2014 году. Посмотреть изменениетам.
Здесь вы можете просто перенаправить stderr, чтобы /dev/null
игнорировать проблему:
wait $pid 2> /dev/null
Обратите внимание, что в:
{ tee $tmpdir/orig | arbitrary_pipeline > $tmpdir/alt; } &
в качестве оптимизации zsh
не будет создавать дополнительный процесс для arbitrary_pipeline
, он будет выполнять его в том же процессе, который запускает эту подоболочку, запущенную в фоновом режиме.
paste
не завершит работу, пока не увидит EOF на своем stdin, его stdin — это канал, куда $pid
(и его потомки, если таковые имеются) пишет на другом конце. Таким образом, он не увидит eof, пока $pid
(и потомки) не закроют все свои файловые дескрипторы (обычно только stdout) на пишущем конце канала. Если только $pid
явно не закроет свой stdout (что бывает очень редко), это произойдет только при выходе.
Это означает paste
, что в большинстве случаев не стоит выходить раньше $pid
, но все равно лучше сделать это wait
на всякий случай.
Обратите внимание, что здесь вы можете использовать a, coproc
чтобы избежать временных fifo:
coproc arbitrary_pipeline
cat >&p | paste - /dev/fd/3 3<&p &
coproc : close
wait
(обратите внимание, что wait
также ждёт coproc
s).