Alvaros
.
- Регистрация
- 14.05.16
- Сообщения
- 21.452
- Реакции
- 101
- Репутация
- 204
$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
Здесь fortune условная программа без exit(rand()).
Cможете объяснить?
Лирично-историческое отступление
Первый раз с этим Гейзенбагом я познакомился четверть века назад. Тогда для шлюза в FaxNET требовалось сделать несколько утилит через
You must be registered for see links
«играющих в шашки» под FreeBSD. Как положено, я считал себя продвинутым и достаточно опытным программистом. Поэтому намеревался сделать всё максимально аккуратно и тщательно, уделив особое внимание обработке ошибок…Усердия в «тщательной обработке ошибок» мне тогда добавил предыдущий опыт борьбы с багами в sendmail и uucp/uupc. Погружаться в детали той истории смысла нет, но боролся с этим Гейзенбагом я недели две по 10-14 часов. Поэтому запомнилось, и вот вчера этот старый знакомый снова заглянул в гости.
TL;DR Ответ
Утилита head может закрыть канал от fortune сразу как только прочитает первую строку. Если fortune выводит более одной строки, то соответствующий вызов write() вернет ошибку, либо сообщит о выводе меньшего количества байт чем запрошено. В свою очередь, написанная с тщательной обработкой ошибок fortune вправе отразить эту ситуацию в своем статусе выхода. Тогда из-за установки set -o pipefail отработает || echo "Вы проиграли".
Однако, head может не успеть закрыть до того, как fortune закончит вывод данных. Тогда отработает && echo "Повезло!".
В одном из моих сегодняшних
You must be registered for see links
есть такой
You must be registered for see links
:echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
В переводе на человеческий
Здесь обычным для
You must be registered for see links
и
You must be registered for see links
способом у компилятора при помощи опции --version спрашивается кто он такой, а если опция не поддерживается, то подставляется заглушка "Please use GCC or CLANG compatible compiler".Подобный
You must be registered for see links
можно встретить где угодно. В этом месте он появился давно и прекрасно везде работал (Linux, Solaris, OSX, FreeBSD,
You must be registered for see links
и т.д.). Но вчера в
You must be registered for see links
на платформе
You must be registered for see links
я заметил:#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\")'"
Через-раз то выдавало лишний текст, то нет… Нередко один из вариантов залипал достаточно долго, но если потыкать подольше то всегда получались оба!
Конечно,
You must be registered for see links
наше всё! И вот набрав strace-тираду, но не успев нажать Enter я узнал своего старого знакомого мистера Гейзенбага, а в разработчиках
You must be registered for see links
себя самого 25 лет назад,
You must be registered for see links
И решил взгрустнуть написать эту заметку Кстати, как любой уважающий себя
You must be registered for see links
, под strace предпочитает не воспроизводиться.Так что же происходит?
- Утилита head вправе (скорее даже вынуждена) закрыть читаемый канал, сразу как только прочитает запрошенное количество строк.
- Генерирующая данные программа-писатель (в данном случае cc) может выводить несколько строк и вольна делать это посредством нескольких вызовов write().
- Если читатель успеет закрыть канал со своей стороны до окончания записи на стороне писателя, то писателю прилетит ошибка.
- Программа-писатель вправе как проигнорировать ошибку записи в канал, так и отразить её в своем коде завершения.
- Вследствие установки set -o pipefail код завершения конвейера будет ненулевым (ошибочным) при ненулевом результате хотя бы от одного элемента, и тогда отработает || echo "Please use GCC or CLANG compatible compiler".
Могут быть вариации, в зависимости от того как программа-писатель работает с сигналами. Например, программа может быть аварийно завершена (с автоматическим формированием ненулевого/ошибочного статуса завершения), либо write() вернет результат о записи меньшего количества байтов чем запрошено и установит errno = EPIPE.
Кто виноват?
Никто, кроме меня в описываемом случае. Обработка ошибок в cc (lcc:1.23.20:Sep--4-2019:e2k-v3-linux) не является избыточной и я не намерен засорять багзиллу. В целом это верный подход, хотя и выявляющий внезапные недочеты в boilerplate.
Что делать?
Неправильно:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
Правильно:
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Либо:
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Пользуясь случаем хочу выразить благодарность коллективам
You must be registered for see links
и
You must be registered for see links
за огромную результативную работу. Ваша целеустремлённость восхищает!Так держать Camarades, до
You must be registered for see links
!КДПВ от
You must be registered for see links



