Почему использование `yes` на конвейерах bash * not * вызывает бесконечные циклы?

16

Согласно документации, bash ожидает завершения всех команд в конвейере, прежде чем продолжить

Оболочка ожидает завершения всех команд в конвейере, прежде чем вернуть значение.

Так почему же команда yes | trueзавершается немедленно? Разве yesцикл не должен вечно вызывать конвейер?


И подзапрос: согласно спецификации POSIX , конвейеры оболочки могут выбрать либо возврат после завершения последней команды, либо ожидание завершения всех команд. Разве обычные оболочки имеют различное поведение в этом смысле? Есть ли снаряды, где yes | trueбудут петли вечно?

hugomg
источник
yes | tee >(true) >/dev/nullбудет делать, как вы ожидаете, между прочим, так teeпродолжается до тех пор, пока все писатели не умрут, поэтому trueвыход не нарушит его полностью.
Чарльз Даффи
1
trueэто в основном {return 0;}программа, поэтому я не ожидаю, что она будет работать долго, не говоря уже о вечности.
Дмитрий Григорьев

Ответы:

33

При trueвыходе сторона чтения канала закрывается, но yesпродолжает пытаться выполнить запись в сторону записи. Это условие называется «сломанный канал», и оно заставляет ядро ​​отправлять SIGPIPEсигнал yes. Поскольку yesничего особенного в этом сигнале не делается, он будет убит. Если он проигнорирует сигнал, его writeвызов завершится ошибкой с кодом ошибки EPIPE. Программы, которые делают это, должны быть готовы заметить EPIPEи прекратить писать, иначе они пойдут в бесконечный цикл.

Если вы сделаете strace yes | true1, вы увидите, что ядро ​​готовится к обеим возможностям:

write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17556, si_uid=1000} ---
+++ killed by SIGPIPE +++

straceнаблюдает за событиями через API отладчика, который сначала сообщает ему о возвращении системного вызова с ошибкой, а затем о сигнале. С yesточки зрения, однако, сигнал происходит первым. (Технически, сигнал доставляется после того, как ядро ​​вернуло управление в пространство пользователя, но до того, как будут выполнены какие-либо машинные инструкции, поэтому writeфункция «обертка» в библиотеке C не получает возможности установить errnoи вернуться в приложение.)


1 К сожалению, straceэто специфично для Linux. В большинстве современных Unix-ов есть какая-то команда, которая делает что-то похожее, но часто имеет другое имя, вероятно, она не декодирует аргументы системного вызова так же тщательно, а иногда она работает только для root.

Casey
источник
3
@hugomg в этом случае, труба совершенно не имеет значения.
Муру
3
@hugomg, потому что ничего не yesсвязано с трубой.
Муру
4
Это правда, что это демонстрация документированного поведения «дождитесь завершения всех команд, прежде чем завершить конвейер». Он просто предотвращает yesполучение SIGPIPE, поскольку FD, на который он пишет, не подключен к каналу.
Том Хант
2
@ Hugomg, это цикл навсегда так же, yes >/dev/nullкак цикл навсегда. Он не демонстрирует ничего о конвейерах, что также не относится к простым командам (как отмечает Том, ожидающий завершения, это относится и к простым командам).
Чарльз Даффи
2
@zwol: я думаю, что мы используем здесь наши термины с несколько иным значением или думаем о вещах с несколько иной точки зрения… но в любом случае write()(функция в libc) не возвращается (после этого передает управление на ПК) до тех пор, пока обработчик сигнала запущен, но поскольку обработчик сигнала завершает программу, управление никогда не передается и, следовательно, write()никогда не возвращается. Да, это реализовано в ядре с помощью xxx_write()возврата некоторой функции -EPIPE, но мы отлаживаем программу пользовательского пространства и не заинтересованы в этом.
Дитрих Эпп
5

Есть ли снаряды где да | правда будет цикл навсегда?

Маловероятно, так как yesкоманда использует канал, и он потерпит неудачу, когда канал будет разорван. sleepс другой стороны, не использует трубу, поэтому:

sleep 100000000 | true

будет работать как минимум 100000000 секунд.

Мур
источник
2
Будьте осторожны со всеми современными оболочками, которые не разветвляются для последней (самой правой) встроенной команды в трубе и где trueнаходится встроенная команда. Это относится и к последним версиям Bourne Shell, ksh93, zsh. Если вы нажмете ^Zво время выполнения такой команды, это приостановит режим сна, и оболочка никогда не сможет восстановиться без внешней помощи.
Щили
3
zsh 4.3.4 (i386-pc-solaris2.11) здесь, так что кажется, что это было недавно изменено. Интересная идея, мне нужно посмотреть, смогу ли я реализовать подобное исправление для Bourne Shell. До сих пор остается вопрос, как это работает, и какая группа процессов tty используется, как в Bourne Shell, тот факт, что самая правая команда является встроенной, обнаруживается после того, как группа процессов для режима сна уже установлена ​​навсегда.
Щили
2
@CharlesDuffy из того, что я понимаю, schily поддерживает версию sh, к которой он приписывает улучшения из современных оболочек. Он написал об этом здесь, где-то.
Муру
3
Оболочка Борна в семейных архивах сохранялась до ~ 2007 года, но никогда не делалась полностью переносимой, поскольку все еще содержит вызовы sbrk(). Портативная и поддерживаемая версия находится в комплекте инструментов schily, и @Charles Duffy уже обнаружил место для информации ;-)
schily
2
@muru многие из функций, которые я перенес в Bourne Shell, взяты из моего bsh(Berthold Shell из VBERTOS, версия UNOS с расширенной виртуальной памятью - первый клон UNIX). Bsh действительно получил много возможностей csh в 1984 и 1985 годах, но механизм псевдонимов в UNOS был лучше, чем в csh в 1980 году. Другие новые функции Bourne Shell - от POSIX, чтобы позволить ему приблизиться к соответствию POSIX.
Щили