$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
Tu fortune
podmienený program bez exit(rand())
.
Môžeš vysvetliť? čo je tu zle?
Lyricko-historická odbočka
Prvýkrát som sa s týmto Heisenbugom zoznámil pred štvrťstoročím. Potom pre bránu vo FaxNET bolo potrebné vytvoriť niekoľko utilít cez
Moje predchádzajúce skúsenosti s riešením chýb v sendmail a uucp/uupc prispeli k mojej usilovnosti pri „dôkladnom spracovaní chýb“. Nemá zmysel ponárať sa do podrobností tohto príbehu, ale s týmto Heisenbugom som bojoval dva týždne 10-14 hodín. Preto sa na to spomenulo a včera sa tento starý známy opäť zastavil na návšteve.
Odpoveď TL;DR
Užitočnosť head
plechovka zatvorte kanál z fortune
сразу hneď ako prečíta prvý riadok. Ak fortune
vypíše viac ako jeden riadok, potom zodpovedajúce volanie write()
vráti chybu alebo hlási, že je na výstupe menej bajtov, ako sa požaduje. Na druhej strane napísané s opatrným spracovaním chýb fortune
má právo zohľadniť túto situáciu vo svojom výstupnom stave. Potom kvôli inštalácii set -o pipefail
bude pracovať || echo "Вы проиграли"
.
Avšak, head
nemusí to stihnúť včas zavrieť predtým fortune
dokončí výstup údajov. Potom to bude fungovať && echo "Повезло!"
.
V jednom z mojich dnešných GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
Preložené do ľudského jazyka
Je to tu bežné pre --version
spýta sa, kto je, a ak možnosť nie je podporovaná, vloží sa stub "Použite kompilátor kompatibilný s GCC alebo CLANG".
ako
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Úprimne povedané, svojho starého „známeho“ som hneď nespoznal. Navyše, projekt už bol mnohokrát testovaný na Elbrus a pod mnohými rôznymi distribúciami, vrátane Alt. S rôznymi kompilátormi, verziami GNU Make a bash. Preto som tu nechcel vidieť svoju chybu.
Pri pokuse reprodukovať problém a/alebo pochopiť, čo sa deje, sa začali diať podivnejšie veci.
Kúzlo príkazového riadku:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
Z času na čas by to produkovalo text navyše, potom nie... Často by jedna z možností trvala dosť dlho, ale ak by ste štukali dlhšie, vždy ste dostali obe!
Samozrejme, strace
Mimochodom, ako každá sebaúcta strace
radšej sa nerozmnožuje.
Tak čo sa deje?
- Užitočnosť
head
má právo (alebo skôr je dokonca nútené) zatvoriť čítaný kanál hneď, ako načíta požadovaný počet riadkov. - Program na vytváranie údajov (v tomto prípade
cc
) plechovka vytlačiť viacero riadkov a zadarmo urobte to prostredníctvom viacerých hovorovwrite()
. - Ak čitateľ bude mať čas zavrieť kanál na svojej strane pred koncom nahrávania na strane zapisovateľa, potom sa autorovi zobrazí chyba.
- Spisovateľský program nárok na obaja ignorujú chybu zápisu kanála a premietnu ju do kódu dokončenia.
- Kvôli inštalácii
set -o pipefail
kód dokončenia potrubia bude nenulový (chybný), ak je výsledok aspoň jedného prvku nenulový, a potom bude fungovať|| echo "Please use GCC or CLANG compatible compiler"
.
Môžu existovať odchýlky v závislosti od toho, ako zapisovací program pracuje so signálmi. Napríklad môže dôjsť k abnormálnemu ukončeniu programu (s automatickým generovaním nenulového/chybového stavu ukončenia), príp write()
vráti výsledok zápisu menšieho počtu bajtov, ako je požadované a nastavené errno = EPIPE
.
Kto je na vine?
V opísanom prípade zo všetkého trochu. Spracovanie chýb v cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nie je nadbytočný. V mnohých prípadoch je lepšie pomýliť sa na strane opatrnosti, hoci to odhaľuje náhle nedostatky v štandardnom modeli navrhnutom pre tradičné správanie.
Čo robiť?
zle:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
opraviť:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
V tomto prípade skoré zatvorenie potrubia bude spracované interpretérom príkazov pri obsluhe vnoreného potrubia ("v rámci" zátvoriek). V súlade s tým, ak
fortune
ohlási chybu v písaní na uzavretý kanál v stave, potom výstup|| echo "Ошибка"
nikam sa nedostane, pretože kanál je už zatvorený. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Tu je pomôcka
cat
funguje ako tlmič, pretože ignoruje chybuEPIPE
pri odvolaní. Toto na záver stačífortune
malý (niekoľko riadkov) a zmestí sa do vyrovnávacej pamäte kanála (od 512 bajtov do ≈64K, vo väčšine OS ≥4K). V opačnom prípade sa problém môže vrátiť.
Ako zvládnuť EPIPE
a iné chyby pri nahrávaní?
Neexistuje jediné správne riešenie, ale existujú jednoduché odporúčania:
EPIPE
potrebný musia byť spracované (a odráža sa v stave ukončenia) pri výstupe údajov, ktoré vyžadujú integritu. Napríklad počas prevádzky archivátorov alebo zálohovacích utilít.EPIPE
lepšie ignorovať pri zobrazovaní informácií a pomocných správ. Napríklad pri zobrazovaní informácií o možnostiach--help
alebo--version
.- Ak sa vyvíjaný kód môže použiť v potrubí predtým
| head
, PotomEPIPE
Je lepšie ignorovať, inak je lepšie spracovať a premietnuť do stavu odchodu.
Chcel by som využiť túto príležitosť a poďakovať tímom
Len tak ďalej Camarades, hore
Vďaka
KDPV od
Zdroj: hab.com