$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
hè fortune
условная программа без exit(rand())
.
Cможете объяснить что здесь глючит?
Лирично-историческое отступление
Первый раз с этим Гейзенбагом я познакомился четверть века назад. Тогда для шлюза в FaxNET требовалось сделать несколько утилит через
Усердия в «тщательной обработке ошибок» мне тогда добавил предыдущий опыт борьбы с багами в sendmail и uucp/uupc. Погружаться в детали той истории смысла нет, но боролся с этим Гейзенбагом я недели две по 10-14 часов. Поэтому запомнилось, и вот вчера этот старый знакомый снова заглянул в гости.
TL;DR Ответ
Utilità head
pò закрыть канал от fortune
сразу как только прочитает первую строку. Если fortune
выводит более одной строки, то соответствующий вызов write()
вернет ошибку, либо сообщит о выводе меньшего количества байт чем запрошено. В свою очередь, написанная с тщательной обработкой ошибок fortune
вправе отразить эту ситуацию в своем статусе выхода. Тогда из-за установки set -o pipefail
отработает || echo "Вы проиграли"
.
Tuttavia, head
может не успеть закрыть до того, как fortune
закончит вывод данных. Тогда отработает && echo "Повезло!"
.
В одном из моих сегодняшних GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
В переводе на человеческий
Здесь обычным для --version
спрашивается кто он такой, а если опция не поддерживается, то подставляется заглушка "Please use GCC or CLANG compatible compiler".
Mi piace
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Признаться я не сразу разглядел старого "знакомого". Более того, проект уже многократно проверялся на "Эльбрусах" и под массой различных дистрибутивов, в том числе с "Альтом". С различными компиляторами, версиями GNU Make и bash. Поэтому своей оплошности я тут видеть никак не хотел.
При попытке воспроизвести проблему и/или понять в чем дело странностей стало больше.
Заклинание в командной строке:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
Через-раз то выдавало лишний текст, то нет… Нередко один из вариантов залипал достаточно долго, но если потыкать подольше то всегда получались оба!
È sicuru, strace
Кстати, как любой уважающий себя strace
предпочитает не воспроизводиться.
Allora chì passa ?
- Utilità
head
вправе (скорее даже вынуждена) закрыть читаемый канал, сразу как только прочитает запрошенное количество строк. - Генерирующая данные программа-писатель (в данном случае
cc
) pò выводить несколько строк и вольна делать это посредством нескольких вызововwrite()
. - se читатель успеет закрыть канал со своей стороны до окончания записи на стороне писателя, то писателю прилетит ошибка.
- Программа-писатель hà u dirittu как проигнорировать ошибку записи в канал, так и отразить её в своем коде завершения.
- Вследствие установки
set -o pipefail
код завершения конвейера будет ненулевым (ошибочным) при ненулевом результате хотя бы от одного элемента, и тогда отработает|| echo "Please use GCC or CLANG compatible compiler"
.
Могут быть вариации, в зависимости от того как программа-писатель работает с сигналами. Например, программа может быть аварийно завершена (с автоматическим формированием ненулевого/ошибочного статуса завершения), либо write()
вернет результат о записи меньшего количества байтов чем запрошено и установит errno = EPIPE
.
Quale hè culpèvule ?
В описываемом случае все понемногу. Обработка ошибок в cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) ùn hè micca избыточной. Во многих случаях лучше перебдеть, хотя это и выявляет внезапные недочеты в boilerplate рассчитанным на традиционное поведение.
Chì deve fà?
sbagliatu:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
Correfica:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Здесь раннее закрытие канала будет обработано интерпретатором команд при обслуживании вложенного конвейера ("внутри" скобок). Соответственно, если
fortune
сообщит в статусе об ошибки записи в закрытый канал, то вывод|| echo "Ошибка"
уже никуда не попадет, так как канал уже закрыт. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Здесь утилита
cat
выступает демпфером, так как игнорирует ошибкуEPIPE
при выводе. Этого достаточно пока выводfortune
небольшой (несколько строк) и помещается в канальном буфере (от 512 байт до ≈64К, в большинстве ОС ≥4К). В противном случае проблема может вернуться.
Как правильно обрабатывать EPIPE
и другие ошибки при записи?
Единственно верного решения нет, но есть простые рекомендации:
EPIPE
necessariu обязательно обрабатывать (и отражать в статусе выхода) при выводе данных требующих целостности. Например, в ходе работы архиваторов или утилит резервного копирования.EPIPE
лучше игнорировать при выводе информационных и вспомогательных сообщений. Например, при выводе информации по опциям--help
o--version
.- Если разрабатываемый код допустимо использовать в конвейере перед
| head
, alluraEPIPE
лучше игнорировать, иначе лучше обрабатывать и отражать в статусе выхода.
Пользуясь случаем хочу выразить благодарность коллективам
Так держать Camarades, до
Спасибо
КДПВ от
Source: www.habr.com