$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
to je fortune
uslovni program bez exit(rand())
.
Možeš li objasniti? šta nije u redu?
Lirsko-istorijska digresija
Prvi put sam se upoznao sa ovim Heisenbugom pre četvrt veka. Zatim je za gateway u FaxNET-u bilo potrebno kreirati nekoliko uslužnih programa putem
Moje prethodno iskustvo bavljenja greškama u sendmailu i uucp/uupc dodalo je mojoj marljivosti u „temeljnom rukovanju greškama“. Nema smisla uranjati u detalje te priče, ali ja sam se sa ovim Heisenbugom mučio dvije sedmice po 10-14 sati. Stoga je ostalo upamćeno, a juče je ovaj stari znanac ponovo svratio u posjetu.
TL;DR odgovor
Utility head
moći zatvorite kanal iz fortune
сразу čim pročita prvi red. Ako fortune
izlazi više od jedne linije, zatim odgovarajući poziv write()
će vratiti grešku ili prijaviti da je izlaz manje bajtova od traženog. Zauzvrat, napisano uz pažljivo rukovanje greškama fortune
ima pravo da ovu situaciju odrazi u svom izlaznom statusu. Zatim zbog instalacije set -o pipefail
će raditi || echo "Вы проиграли"
.
Međutim, head
možda neće stići na vrijeme zatvori prije fortune
će završiti sa izlazom podataka. Onda će raditi && echo "Повезло!"
.
U jednom od mojih današnjih GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
Prevedeno na ljudski
Ovde je uobičajeno --version
pita ko je on, a ako opcija nije podržana, onda se ubacuje stub "Molimo koristite GCC ili CLANG kompatibilan kompajler".
poput
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Iskreno, nisam odmah prepoznao svog starog "poznanika". Štaviše, projekat je već testiran mnogo puta na Elbrusu i pod mnogo različitih distribucija, uključujući Alt. Sa raznim kompajlerima, verzijama GNU Make i bash. Stoga, nisam želio ovdje vidjeti svoju grešku.
Prilikom pokušaja da se reproducira problem i/ili shvati šta se dešava, počele su se dešavati još čudnije stvari.
Čarolija komandne linije:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
S vremena na vrijeme proizveo bi dodatni tekst, a onda ne... Često bi se jedna od opcija držala prilično dugo, ali ako bi dulje pickali, uvijek ste dobili obje!
Naravno, strace
Usput, kao i svako samopoštovanje strace
preferira da se ne razmnožava.
Pa šta se dešava?
- Utility
head
ima pravo (tačnije, čak je prinuđen) da zatvori kanal koji se čita čim pročita traženi broj redova. - Program za generiranje podataka (u ovom slučaju
cc
) moći ispisati više redova i besplatno uradite to putem više pozivawrite()
. - ako čitalac će imati vremena da zatvori kanal na svojoj strani prije kraja snimanja na strani pisca, tada će pisac dobiti grešku.
- Program za pisanje ima pravo oba ignoriraju grešku pisanja kanala i odražavaju je u vašem kodu za završetak.
- Zbog instalacije
set -o pipefail
kod završetka cjevovoda će biti različit od nule (pogrešan) ako je rezultat različit od nule iz barem jednog elementa, i tada će raditi|| echo "Please use GCC or CLANG compatible compiler"
.
Mogu postojati varijacije ovisno o tome kako program za pisanje radi sa signalima. Na primjer, program se može nenormalno prekinuti (sa automatskim generiranjem statusa završetka koji nije nula/greška), ili write()
će vratiti rezultat pisanja manjeg broja bajtova od traženog i postavljenog errno = EPIPE
.
Ko je kriv?
U opisanom slučaju od svega po malo. Greška pri rukovanju cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nije suvišan. U mnogim slučajevima je bolje pogriješiti na strani opreza, iako to otkriva iznenadne nedostatke u šablonu dizajniranom za tradicionalno ponašanje.
Što da radim?
Pogrešno:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
Tačno:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Ovdje će rano zatvaranje cijevi biti obrađeno od strane interpretatora komande prilikom servisiranja ugniježđenog cjevovoda ("unutar" zagrada). Shodno tome, ako
fortune
će prijaviti grešku u pisanoj formi zatvorenom kanalu u statusu, zatim izlaz|| echo "Ошибка"
neće stići nikuda, pošto je kanal već zatvoren. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Evo uslužnog programa
cat
djeluje kao amortizer jer ignorira greškuEPIPE
po povlačenju. Ovo je dovoljno za sada zaključakfortune
mali (nekoliko linija) i stane u bafer kanala (od 512 bajtova do ≈64K, u većini OS ≥4K). U suprotnom se problem može vratiti.
Kako postupati EPIPE
i druge greške u snimanju?
Ne postoji jedno pravo rješenje, ali postoje jednostavne preporuke:
EPIPE
potrebno moraju biti obrađene (i odražava se u izlaznom statusu) pri izlasku podataka koji zahtijevaju integritet. Na primjer, tokom rada arhivatora ili uslužnih programa za pravljenje rezervnih kopija.EPIPE
bolje ignorisati prilikom prikaza informacija i pomoćnih poruka. Na primjer, kada se prikazuju informacije o opcijama--help
ili--version
.- Ako se kod koji se razvija može koristiti u cjevovodu prije
| head
, ondaEPIPE
Bolje je zanemariti, inače je bolje obraditi i odraziti u izlaznom statusu.
Iskoristio bih priliku da izrazim zahvalnost timovima
Samo tako, Kamaradi, gore
Spasibo
KDPV iz
izvor: www.habr.com