Упоримся? А давай!
Открывай терминал с башем и запускай команду:
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.
Теперь как это пофиксить.
Вот несколько вариантов:
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.
Сложно? Да не… азы с некоторыми нюансами. Изучай!








Комментарии