$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
ọ bụ fortune
условная программа без exit(rand())
.
Cможете объяснить что здесь глючит?
Лирично-историческое отступление
Первый раз с этим Гейзенбагом я познакомился четверть века назад. Тогда для шлюза в FaxNET требовалось сделать несколько утилит через
Усердия в «тщательной обработке ошибок» мне тогда добавил предыдущий опыт борьбы с багами в sendmail и uucp/uupc. Погружаться в детали той истории смысла нет, но боролся с этим Гейзенбагом я недели две по 10-14 часов. Поэтому запомнилось, и вот вчера этот старый знакомый снова заглянул в гости.
TL;DR Ответ
Ịbara uru head
ike закрыть канал от fortune
ozugbo как только прочитает первую строку. Если fortune
выводит более одной строки, то соответствующий вызов write()
вернет ошибку, либо сообщит о выводе меньшего количества байт чем запрошено. В свою очередь, написанная с тщательной обработкой ошибок fortune
вправе отразить эту ситуацию в своем статусе выхода. Тогда из-за установки set -o pipefail
отработает || echo "Вы проиграли"
.
Otú ọ dị, 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".
Dị ka
#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")'"
Через-раз то выдавало лишний текст, то нет… Нередко один из вариантов залипал достаточно долго, но если потыкать подольше то всегда получались оба!
N'ezie, strace
Кстати, как любой уважающий себя strace
предпочитает не воспроизводиться.
Yabụ kedu ihe na-eme?
- Ịbara uru
head
вправе (скорее даже вынуждена) закрыть читаемый канал, сразу как только прочитает запрошенное количество строк. - Генерирующая данные программа-писатель (в данном случае
cc
) ike выводить несколько строк и вольна делать это посредством нескольких вызововwrite()
. - ma ọ bụrụ na читатель успеет закрыть канал со своей стороны до окончания записи на стороне писателя, то писателю прилетит ошибка.
- Программа-писатель nwere ikike как проигнорировать ошибку записи в канал, так и отразить её в своем коде завершения.
- Вследствие установки
set -o pipefail
код завершения конвейера будет ненулевым (ошибочным) при ненулевом результате хотя бы от одного элемента, и тогда отработает|| echo "Please use GCC or CLANG compatible compiler"
.
Могут быть вариации, в зависимости от того как программа-писатель работает с сигналами. Например, программа может быть аварийно завершена (с автоматическим формированием ненулевого/ошибочного статуса завершения), либо write()
вернет результат о записи меньшего количества байтов чем запрошено и установит errno = EPIPE
.
Kedu onye ikpe mara?
В описываемом случае все понемногу. Обработка ошибок в cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) abụghị избыточной. Во многих случаях лучше перебдеть, хотя это и выявляет внезапные недочеты в boilerplate рассчитанным на традиционное поведение.
Kedu ihe m kwesịrị ime?
ezighi ezi:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
N'ụzọ ziri ezi:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Здесь раннее закрытие канала будет обработано интерпретатором команд при обслуживании вложенного конвейера ("внутри" скобок). Соответственно, если
fortune
сообщит в статусе об ошибки записи в закрытый канал, то вывод|| echo "Ошибка"
уже никуда не попадет, так как канал уже закрыт. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Здесь утилита
cat
выступает демпфером, так как игнорирует ошибкуEPIPE
при выводе. Этого достаточно пока выводfortune
небольшой (несколько строк) и помещается в канальном буфере (от 512 байт до ≈64К, в большинстве ОС ≥4К). В противном случае проблема может вернуться.
Как правильно обрабатывать EPIPE
и другие ошибки при записи?
Единственно верного решения нет, но есть простые рекомендации:
EPIPE
achọrọ обязательно обрабатывать (и отражать в статусе выхода) при выводе данных требующих целостности. Например, в ходе работы архиваторов или утилит резервного копирования.EPIPE
лучше игнорировать при выводе информационных и вспомогательных сообщений. Например, при выводе информации по опциям--help
ma ọ bụ--version
.- Если разрабатываемый код допустимо использовать в конвейере перед
| head
mgbe ahụEPIPE
лучше игнорировать, иначе лучше обрабатывать и отражать в статусе выхода.
Пользуясь случаем хочу выразить благодарность коллективам
Так держать Camarades, до
Achọpụta
КДПВ от
isi: www.habr.com