Мне встречалось множество рекомендаций о повышении безопасности использования shell-скриптов в Bash путём включения опции
Для того чтобы понять суть этой проблемы давайте сначала разберёмся с тем, за что именно отвечает опция Bash
Статусом выхода из конвейера, в том случае, если не включена опция
Причина использования
К сожалению, именно тут на горизонте появляется наш старый друг SIGPIPE. Роль
Команда
(В некоторых случаях то, что происходит, может, от запуска к запуску, меняться. Причина этого — в системе планирования выполнения процессов. Это может зависеть и от того, какой объём данных производят процессы, находящиеся ближе к началу конвейера, и как он соотносится с тем, что фильтруют процессы, расположенные ближе к концу конвейера. Так, если в нашем примере
Это ведёт к двум шаблонам использования конвейеров командной оболочки. При использовании первого конвейер потребляет все входные данные, действуя так в тех случаях, если всё работает без сбоев. Так как подобным образом работают все процессы — ни у одного процесса никогда не должно возникнуть необходимости выполнять запись в закрытый конвейер. А значит — никогда не появится и сигнал
Конечно, пользоваться подобными конвейерами можно и в окружении, где применяется
Но об этой проблеме нужно помнить, нужно обращать внимание на то, какие команды могут окончить работу раньше, чем обычно, не читая всех входных данных. Если что-то упустить — то в «награду» за это, вероятно, можно получить ошибку из скрипта. Если автору скрипта будет сопутствовать успех, то это будет ошибка, возникающая регулярно. В противном случае ошибки будут возникать время от времени, случаясь тогда, когда одна из команд конвейера выдаёт необычно большой объём выходных данных, или тогда, когда другая команда завершает свою работу необычно рано или необычно быстро.
(Кроме того, очень хорошо было бы игнорировать только ошибки, связанные с
Чутьё подсказывает мне, что шаблон использования конвейеров, основанный на полном потреблении всех входных данных, распространён гораздо сильнее, чем шаблон, когда работа конвейера завершается раньше, чем обычно. Правда, я не пытался оценить свои скрипты на предмет особенностей использования в них конвейеров. Это, определённо, совершенно естественный шаблон использования конвейеров, когда в них выполняется фильтрация, трансформация или исследование всех сущностей из некоего набора (например — для того чтобы их посчитать или вывести некие сводные данные по ним).
pipefail
(например — это рекомендуется в данном материале 2015 года). Это, с одной стороны, хорошая рекомендация. Но включение pipefail
может привести к конфликту. В одном из двух сценариев использования конвейеров эта опция показывает себя замечательно, а вот в другом то, к чему приводит её включение, выглядит просто ужасно.Для того чтобы понять суть этой проблемы давайте сначала разберёмся с тем, за что именно отвечает опция Bash
pipefail
. Обратимся к документации:Статусом выхода из конвейера, в том случае, если не включена опция
pipefail
, служит статус завершения последней команды конвейера. Если опция pipefail
включена — статус выхода из конвейера является значением последней (самой правой) команды, завершённой с ненулевым статусом, или ноль — если работа всех команд завершена успешно.Причина использования
pipefail
заключается в том, что иначе команда, неожиданно завершившаяся с ошибкой и находящаяся где-нибудь в середине конвейера, обычно остаётся незамеченной. Она, если использовалась опция set -e
, не приведёт к аварийному завершению скрипта. Можно пойти другим путём и тщательно проверять всё с использованием $PIPESTATUS
, но это означает необходимость выполнения больших объёмов дополнительной работы.К сожалению, именно тут на горизонте появляется наш старый друг SIGPIPE. Роль
SIGPIPE
в конвейерах заключается в том, чтобы принуждать процессы к остановке в том случае, если они делают попытки записи в закрытый конвейер. Это происходит в том случае, если процесс, расположенный ближе к концу конвейера, не потребил все входные данные. Например, предположим, что нужно обработать первую тысячу строк выходных данных некоей сущности:generate --thing | sed 1000q | gronkulate
Команда
sed
, после получения 1000 строк, завершит работу и закроет конвейер, в который пишет данные generate
. А generate
получит сигнал SIGPIPE
и, по умолчанию, остановится. Статус выхода команды будет отличаться от нуля, а это значит, что с использованием pipefail
работа всего конвейера «завершится с ошибкой» (а с использованием set -e
скрипт нормально завершит работу).(В некоторых случаях то, что происходит, может, от запуска к запуску, меняться. Причина этого — в системе планирования выполнения процессов. Это может зависеть и от того, какой объём данных производят процессы, находящиеся ближе к началу конвейера, и как он соотносится с тем, что фильтруют процессы, расположенные ближе к концу конвейера. Так, если в нашем примере
generate
создаст 1000 строк или меньше — sed
примет все эти данные.)Это ведёт к двум шаблонам использования конвейеров командной оболочки. При использовании первого конвейер потребляет все входные данные, действуя так в тех случаях, если всё работает без сбоев. Так как подобным образом работают все процессы — ни у одного процесса никогда не должно возникнуть необходимости выполнять запись в закрытый конвейер. А значит — никогда не появится и сигнал
SIGPIPE
. Второй шаблон использования конвейеров предусматривает ситуацию, когда хотя бы один процесс завершает обработку входных данных раньше, чем обычно. Часто подобные процессы специально помещают в конвейер для остановки обработки данных в определённый момент (как sed
в вышеприведённом примере). Подобные конвейеры иногда или даже всегда генерируют сигналы SIGPIPE
, некоторые процессы в них завершаются с ненулевым кодом.Конечно, пользоваться подобными конвейерами можно и в окружении, где применяется
pipefail
, и даже с set -e
. Например, можно сделать так, чтобы один из шагов конвейера всегда сообщал бы об успешном завершении:(generate --thing || true) | sed 1000q | gronkulate
Но об этой проблеме нужно помнить, нужно обращать внимание на то, какие команды могут окончить работу раньше, чем обычно, не читая всех входных данных. Если что-то упустить — то в «награду» за это, вероятно, можно получить ошибку из скрипта. Если автору скрипта будет сопутствовать успех, то это будет ошибка, возникающая регулярно. В противном случае ошибки будут возникать время от времени, случаясь тогда, когда одна из команд конвейера выдаёт необычно большой объём выходных данных, или тогда, когда другая команда завершает свою работу необычно рано или необычно быстро.
(Кроме того, очень хорошо было бы игнорировать только ошибки, связанные с
SIGPIPE
, но не другие ошибки. Если generate
завершится с ошибкой по причинам, не связанным с SIGPIPE
, то нам хотелось бы, чтобы весь конвейер выглядел бы так, как если бы он завершился с ошибкой.)Чутьё подсказывает мне, что шаблон использования конвейеров, основанный на полном потреблении всех входных данных, распространён гораздо сильнее, чем шаблон, когда работа конвейера завершается раньше, чем обычно. Правда, я не пытался оценить свои скрипты на предмет особенностей использования в них конвейеров. Это, определённо, совершенно естественный шаблон использования конвейеров, когда в них выполняется фильтрация, трансформация или исследование всех сущностей из некоего набора (например — для того чтобы их посчитать или вывести некие сводные данные по ним).
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
В блоге автора этой статьи есть ещё много подобных материалов. Как вы думаете, стоит ли нам перевести некоторые из них?
87.76% Да43
12.24% Нет6
Проголосовали 49 пользователей. Воздержались 12 пользователей.