Упоримся? А давай!

Открывай терминал с башем и запускай команду:

while :; do cat /etc/hosts ; done | grep -m1 'a'

По идее тут что-то вроде бесконечного цикла, который будет cat’ить на экран файл hosts, предварительно грепнув содержимое по символу «a».

Запустил? И всё зависло! По крайней мере у меня вывелась только одна строчка вида:

127.0.0.1 localhost bashdays

Но почему оно зависло? А потому, что утилита cat при попытке записать данные в сломанный канал, завершается сигналом.

Давай убедимся:

set -o pipefail
cat /etc/hosts | exit 0
echo $(( $? > 128 ? $? - 128 : $? ))

Получаем цифру 13. Откуда она взялась? Смотри! Если команда завершается сигналом, тогда статус ошибки ($?) == 128 + Номер сигнала.

А дальше вычитаем, делим, умножаем, корень квадратный вычисляем, заводим в дискриминант по синусу. Пеню. Короче получили цифру 13.

Более подробно про сигналы мы с тобой разбирали в этом посте.

Давай посмотрим, что такое 13:

kill -l 13

Вывелось PIPE, что равносильно SIGPIPE (Broken Pipe). Ага!

Получается что наш бесконечный цикл постоянно запускает утилиту cat, которая тут же завершается.

Раньше эта ситуация была очевидна и на экран выводилась ошибка. А вот с версии Bash 3.1-alpha1 это пофиксили, вот выжимка из спеков:

This document details the changes between this version, bash-3.1-alpha1,
and the previous version, bash-3.0-release.  

By default, the shell no longer reports processes dying from SIGPIPE.

Нахуй так сделали, непонятно, скорее всего ушли в сторону set pipefail, типа кому надо тот включит, чтобы лишнее гавно на экран не сыпалось.

Ладно. После запуска этого бесконечного цикла, можешь запустить htop и визуально глянуть как порождается бесконечное количество процессов, растет счетчик PID. А как только выработан лимит PID, пиды начинают использоваться повторно. В общем это хорошо видно.

Ну либо так можно глянуть:

ps --no-headers --sort=pid -eo pid

Увидишь большую разницу между значениями PID.

Теперь как это пофиксить.

Роман Шубин
Роман Шубин
CEO & CTO, Главред в «Цифровой улей»
Задать вопрос
Если уж захотелось упороться, нужно упарываться правильно.

Вот несколько вариантов:

while : ; do cat /etc/hosts || { echo $? >&2 ; exit; } ;done | grep -m1 'a'
while : ; do cat /etc/hosts || break ; done | grep -m1 'a'
while : ; do cat /etc/hosts || exit ; done | grep -m1 'a'

Если присмотреться, может возникнуть вопрос — а откуда выходит exit? Цикл выполняется в подоболочке, потому что является участником канала.

Давай проверим:

while : ; do cat ; done | grep -m1 'a' &

Запускаем конвейер в фоне. Команда cat без указания аргументов будет читать стандартный ввод, который в данной ситуации ассоциирован с терминалом и ждет ввода данных.

А такое непозволительно фоновым процессам, поэтому такие задания будут приостановлены.

Проверяем, что оно действительно приостановлено:

jobs -l

[1]+251913 Stopped (tty input) while :; do
cat;
done
251921 | grep --color=auto -m1 'a'

Ну и теперь смотрим, кто есть кто:

ps --forest -o pid,ppid,stat,comm

PID PPID STAT COMMAND
251893  251854 Ss bash
251913  251893 T   \_bash
251924  251913 T    |  \_cat
251921  251893 T    \_grep
251962  251893 R+   \_ps

Процесс 251913/251893 и есть подоболочка, в которой крутится наш цикл. Вот с этой подоболочки и выходит exit.

Сложно? Да не… азы с некоторыми нюансами. Изучай!