echo one; echo two > >(cat); echo three;
Команда дает неожиданный вывод.
Я прочитал это: Как замена процесса реализована в Bash? и много других статей о замене процесса в Интернете, но не понимаю, почему он так себя ведет.
Ожидаемый результат:
one
two
three
Реальный выход:
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
Кроме того, эти две команды должны быть эквивалентны с моей точки зрения, но они не:
##### first command - the pipe is used.
prompt$ seq 1 5 | cat
1
2
3
4
5
##### second command - the process substitution and redirection are used.
prompt$ seq 1 5 > >(cat)
prompt$ 1
2
3
4
5
Почему я думаю, они должны быть одинаковыми? Потому что оба соединяют seq
вывод с cat
входом через анонимный канал - Википедия, Процесс замещения .
Вопрос: почему так себя ведет? Где моя ошибка? Требуется исчерпывающий ответ (с объяснением того, как это bash
делается под капотом).
bash
process-substitution
MiniMax
источник
источник
Ответы:
Да, в том же
bash
духеksh
(откуда берется эта особенность) процессы внутри подстановки процессов не ожидаются (перед выполнением следующей команды в сценарии).для
<(...)
одного, это обычно хорошо, как в:оболочка будет ожидать
cmd1
иcmd1
будет обычно ожидать, посколькуcmd2
она читает до конца файла в канале, который заменяется, и этот конец файла обычно происходит, когдаcmd2
умирает. Это та же причина , несколько снарядов (неbash
) не беспокоить ждутcmd2
вcmd2 | cmd1
.Для
cmd1 >(cmd2)
, однако, что это вообще не так, как это более ,cmd2
что , как правило , ждетcmd1
там так будет вообще выход после.Это исправлено в
zsh
том, что ждетcmd2
там (но не, если вы пишете это какcmd1 > >(cmd2)
иcmd1
не встроено, используйте{cmd1} > >(cmd2)
вместо этого как документировано ).ksh
не ждет по умолчанию, но позволяет ждать его с помощьюwait
встроенного (это также делает pid доступным в$!
, хотя это не поможет, если вы это сделаетеcmd1 >(cmd2) >(cmd3)
)rc
(сcmd1 >{cmd2}
синтаксисом), так же, какksh
вы можете получить pids всех фоновых процессов с$apids
.es
(также сcmd1 >{cmd2}
) ждет,cmd2
как вzsh
, а также ждетcmd2
<{cmd2}
перенаправления в процессе.bash
делает pidcmd2
(или, точнее, подоболочки, когда она запускаетсяcmd2
в дочернем процессе этого subshell, даже если это последняя команда)$!
, но не позволяет вам ждать его.Если вам нужно использовать
bash
, вы можете обойти проблему, используя команду, которая будет ожидать обеих команд с:Это делает оба,
cmd1
иcmd2
их FD 3 открыты для трубы.cat
будет ожидать конца файла на другом конце, поэтому обычно будет выходить только тогда, когда обаcmd1
иcmd2
мертвы. И оболочка будет ждать этогоcat
команды. Вы можете видеть, что в качестве сети можно отследить завершение всех фоновых процессов (вы можете использовать его для других вещей, запускаемых в фоновом режиме, таких как&
, например, с помощью coprocs или даже команд, которые работают в фоновом режиме при условии, что они не закрывают все свои файловые дескрипторы, как это обычно делают демоны). ).Обратите внимание, что благодаря упомянутому выше потраченному впустую процессу подоболочки, он работает, даже если
cmd2
закрывает свой fd 3 (команды обычно этого не делают, но некоторые любятsudo
илиssh
делают). Будущие версииbash
могут в конечном итоге сделать оптимизацию там, как и в других оболочках. Тогда вам нужно что-то вроде:Чтобы убедиться, что еще есть дополнительный процесс оболочки с этим открытым fd 3, ожидающий эту
sudo
команду.Обратите внимание, что
cat
ничего не будет читать (поскольку процессы не пишут на своем fd 3). Это просто для синхронизации. Он сделает только одинread()
системный вызов, который в конце вернется ни с чем.Вы можете избежать запуска
cat
, используя подстановку команд для синхронизации канала:На этот раз это оболочка, а не
cat
та, которая читает из канала, другой конец которого открыт на fd 3 ofcmd1
иcmd2
. Мы используем присвоение переменной, поэтому статус выходаcmd1
доступен в$?
.Или вы можете выполнить подстановку процесса вручную, а затем даже использовать систему, так
sh
как это станет стандартным синтаксисом оболочки:хотя обратите внимание, как отмечалось ранее, что не все
sh
реализации будут ждатьcmd1
послеcmd2
завершения (хотя это лучше, чем наоборот). Это время$?
содержит статус выходаcmd2
; хотяbash
иzsh
сделатьcmd1
состояние выхода доступным в${PIPESTATUS[0]}
и$pipestatus[1]
соответственно (см. такжеpipefail
параметр в нескольких оболочках, чтобы$?
можно было сообщать о сбое компонентов трубы, отличных от последнего)Обратите внимание, что
yash
есть похожие проблемы с функцией перенаправления процесса .cmd1 >(cmd2)
будет написаноcmd1 /dev/fd/3 3>(cmd2)
там. Ноcmd2
его не ждут, и вы тоже не можетеwait
его ждать, и его pid также не доступен в$!
переменной. Вы бы использовали те же обходные пути, что и дляbash
.источник
echo one; { { echo two > >(cat); } 3>&1 >&4 4>&- | cat; } 4>&1; echo three;
, затем упростил его доecho one; echo two > >(cat) | cat; echo three;
и он выводит значения в правильном порядке. Нужны ли все эти дескрипторные манипуляции3>&1 >&4 4>&-
? Кроме того, я не понимаю этого>&4 4>&
- мы перенаправляемstdout
на четвертый раздел, затем закрываем четвертый раздел и снова используем4>&1
его. Зачем это нужно и как это работает? Может быть, я должен создать новый вопрос на эту тему?cmd1
иcmd2
, смысл небольшого танца с файловым дескриптором - восстановить исходные и использовать только дополнительный канал для ожидания вместо того, чтобы также направлять вывод команд.4>&1
создает дескриптор файла (fd) 4 для списка команд внешних фигурных скобок и делает его равным стандартному выводу внешних фигурных скобок. Внутренние скобки имеют автоматическую настройку stdin / stdout / stderr для подключения к внешним скобкам. Тем не менее,3>&1
заставляет fd 3 подключаться к stdin внешних фигурных скобок.>&4
соединяет стандартный вывод внутренних скобок с внешними скобками fd 4 (тот, который мы создали ранее).4>&-
закрывает fd 4 из внутренних фигурных скобок (поскольку стандартный вывод внутренних фигурных скобок уже связан с fd 4 из внешних фигурных скобок).4>&1
выполняется сначала, перед другими перенаправлениями, поэтому вы не «снова используете4>&1
». В целом, внутренние скобки отправляют данные на свой стандартный вывод, который был перезаписан с учетом того, что fd 4 было дано. Fd 4, который был задан внутренними скобками, это fd 4 внешних скобок, который равен исходному stdout внешних скобок.4>5
означает «4 идет к 5», но на самом деле «fd 4 перезаписывается с помощью fd 5». И перед выполнением, fd 0/1/2 автоматически подключаются (вместе с любым fd внешней оболочки), и вы можете перезаписать их, как пожелаете. Это, по крайней мере, моя интерпретация документации bash. Если вы поняли что-то еще из этого , lmk.Вы можете
cat
направить вторую команду в другую , которая будет ждать, пока ее входной канал не закроется. Пример:Коротко и просто.
==========
Как бы просто это ни казалось, многое происходит за кулисами. Вы можете игнорировать остальную часть ответа, если вам не интересно, как это работает.
Когда у вас есть
echo two > >(cat); echo three
,>(cat)
он отключается интерактивной оболочкой и работает независимо отecho two
. Таким образом,echo two
заканчивается, а затемecho three
исполняется, но до>(cat)
окончания. Когдаbash
получает данные,>(cat)
когда они этого не ожидали (через пару миллисекунд), это дает вам ситуацию, похожую на подсказку, когда вам нужно нажать на новую строку, чтобы вернуться в терминал (То же самое, как если бы другой пользовательmesg
вас редактировал).Тем не менее, учитывая
echo two > >(cat) | cat; echo three
, две подоболочки порождены (согласно документации|
символа).Одна подоболочка с именем A предназначена для
echo two > >(cat)
, а одна подоболочка с именем B предназначена дляcat
. A автоматически подключается к B (стандартный вывод A - стандартный B). Затемecho two
и>(cat)
приступайте к выполнению.>(cat)
Stdout 's установлен на стандартный вывод A, который равен стандартному выводу B'. Послеecho two
завершения A выходит, закрывая свой стандартный вывод. Тем не менее,>(cat)
до сих пор держит ссылку на стандартный ввод Б. Stdin второгоcat
содержит stdin B, иcat
он не выйдет, пока не увидит EOF. EOF дается только тогда, когда никто больше не открывает файл в режиме записи, поэтому>(cat)
stdout блокирует второйcat
. B ждет в эту секундуcat
. Послеecho two
выхода очищает буфер и завершает работу. Никто не держит Б / сек>(cat)
конце концов получает EOF, так>(cat)
cat
ввод, поэтому второйcat
читает EOF (B вообще не читает свой стандартный вывод, ему все равно). Этот EOF заставляет секундуcat
очищать свой буфер, закрывать стандартный вывод и завершать работу, а затем B завершается, поскольку завершаетсяcat
и B ожидаетcat
.Предостережение в том, что bash также порождает подоболочку
>(cat)
! Из-за этого вы увидите, чтоecho two > >(sleep 5) | cat; echo three
все еще будет ждать 5 секунд перед выполнением
echo three
, даже еслиsleep 5
не удерживает стандартный ввод B. Это связано с тем, что скрытый подоболочек C, для>(sleep 5)
которого он был создан, ожидаетsleep
, а C держит стандартный ввод B. Вы можете увидеть, какecho two > >(exec sleep 5) | cat; echo three
Однако не будет ждать, так
sleep
как не удерживает стандартный ввод B, и нет призрачной подоболочки C, которая содержит стандартный вывод B (exec заставит sleep заменить C, в отличие от разветвления и заставления C ждатьsleep
). Независимо от этого предостережения,echo two > >(exec cat) | cat; echo three
все равно будет правильно выполнять функции в порядке, как описано ранее.
источник
A
не дожидаясьcat
порождал в>(cat)
. Как я уже упоминал в своем ответе, причина, по которойecho two > >(sleep 5 &>/dev/null) | cat; echo three
выходные данныеthree
через 5 секунд состоят в том, что текущие версииbash
тратят впустую дополнительный процесс оболочки,>(sleep 5)
который ожидает,sleep
и у этого процесса все еще есть стандартный вывод,pipe
который препятствует завершению второгоcat
. Если вы замените егоecho two > >(exec sleep 5 &>/dev/null) | cat; echo three
на этот дополнительный процесс, вы обнаружите, что он сразу же возвращается.echo two > >(sleep 5 &>/dev/null)
как минимум получает свою собственную оболочку. Это недокументированная деталь реализации, которая также заставляетsleep 5
получать свою собственную подоболочку? Если это задокументировано, то это будет законный способ сделать это с меньшим количеством символов (если только нет замкнутого цикла, я не думаю, что кто-то заметит проблемы с производительностью с подоболочкой или кошкой) `. Если это не задокументировано, тогда rip, хороший хак, не будет работать на будущих версиях.$(...)
,<(...)
действительно включает в себя подоболочку, но ksh93 или zsh будут запускать последнюю команду в этой подоболочке в том же процессе,bash
поэтому еще один процесс удерживает канал открытым, в то времяsleep
как выполняется не удерживая канал открытым. В будущих версияхbash
может быть реализована аналогичная оптимизация.exec
он работает, он работает как ожидалось.