Pipe Fail (141) при выходе трубопровода в тройник – почему?

Пример должен уточнить мой вопрос. Такое поведение имеет смысл для меня:

$ echo hi | cat hi $ echo hi | tee >(cat) hi hi 

Первый случай очевиден. Во втором случае мы передаем «hi» в tee, используя подстановку команд, и один «hi» печатается tee 'd cat , в то время как другой печатается по проходу tee через трубу. Все идет нормально…

Но что происходит с первым «привет» в этом случае:

 $ echo hi | tee >(echo yo) yo 

Код возврата – 141, сбой в трубе. Что может быть причиной этого?

Я запускаю Mac OSX El Capitain, bash в приложении по умолчанию для терминала

Я думаю, что я понял, как настроить ваш опыт, чтобы превратить его во что-то, что другие люди смогут воспроизвести:

  $ (echo hello; sleep 1; мир эха) |  tee> (кошка)
 Здравствуйте
 привет ... и, после небольшой задержки,
 Мир
 Мир

 $ echo "$?"
 0

 $ (echo hello; sleep 1; мир эха) |  tee> (echo yo)
 Эй
 Здравствуйте

 $ echo "$?"
 141 

Как вы, надеюсь, понимаете, > ( command ) создает канал для command запуска процесса. Стандартный ввод command связан с именем пути, которое могут открывать и записывать другие команды в командной строке (в данном случае, tee ). Когда command является cat , процесс находится там и читается с stdin, пока не получит EOF. В этом случае у tee нет проблем записывать все данные, которые он считывает со своего stdin в трубу.

Но, когда command echo yo , процесс записывает yo в stdout и сразу же выходит. Это вызывает проблему для tee ; когда он пишет в трубу без процесса на другом конце, он получает сигнал SIGPIPE.

По-видимому, версия tee OS X записывается в файл (ы) в командной строке сначала, а затем его stdout. Итак, в вашем примере ( echo hi | tee >(echo yo) ), tee получает отказ канала при его самой первой записи. В то время как версия tee на Linux и Cygwin сначала записывается на stdout, поэтому ему удается написать hi на экран, прежде чем он умрет. В моем расширенном примере tee умирает, когда он пишет hello в трубу, поэтому у него нет возможности читать и писать world .

Чтобы увидеть, что происходит, сравните следующие два варианта:

 bash -c 'echo hi | tee >(sleep 1; echo yo); echo $?' bash -c 'wait_and_tee () { sleep 1; tee "$@"; }; echo hi | wait_and_tee >(echo yo); echo $?' 

Заметьте, что происходит в первом варианте?

 $ bash -c 'echo hi | tee >(sleep 1; echo yo); echo $?' hi 0 $ yo 

Команда в замещении процесса sleep 1; echo yo sleep 1; echo yo выполняется параллельно с командами снаружи, а bash не ждет завершения. Таким образом, последовательность событий:

  • Три команды echo hi , sleep 1; echo yo sleep 1; echo yo и tee запускаются параллельно.
  • echo hi пишет hi к его выходу (the | pipe).
  • tee считывает из канала и записывает как его стандартный вывод, так и его аргумент командной строки, который является другим каналом, созданным с помощью >(…) . В результате получается одна копия hi напечатанная на терминале, и одна копия в буфере канала >(…) .
  • Параллельно с предыдущей маркерной точкой echo hi выходит, что закрывает конец записи | труба.
  • tee замечает, что он достиг своего конца входного файла. Он написал все свои данные, поэтому он выходит.
  • С точки зрения Баша, обе стороны трубы вышли, поэтому команда закончена. Поскольку tee возвращается 0, статус конвейера равен 0.
  • Спустя секунду, sleep 1 заканчивается, и выполняется echo yo .

Во втором варианте я заставляю echo yo прекращаться до tee , заставляя его прекращаться до начала tee . На этот раз последовательность событий:

  • Три команды echo hi , echo yo и sleep 1 запускаются параллельно.
  • echo hi пишет hi к его выходу (the | pipe).
  • Параллельно с предыдущей маркерной точкой echo yo печатает yo и выходит.
  • Через секунду sleep 1 и tee .
  • tee считывает hi от своего ввода и пытается записать его как на терминал (его стандартный вывод), так и на канал, полученный в результате >(…) переданного в качестве аргумента. Поскольку процесс, который открыл этот канал для чтения ( echo yo ), вышел из второй попытки, попытка записи в трубу завершилась неудачей с SIGPIPE (сигнал 13, который оболочка сообщает как 128 + signal_number ).

Как объясняет G-Man , будет ли hi отображаться во втором случае, зависит от того, пытается ли tee сначала записать на свой стандартный вывод или его аргумент файла.

Без вызовов sleep время может идти в любом случае (я получаю примерно половину / половину под Linux, другое ядро ​​может сделать одно время намного более вероятным, чем другое).