$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
Her fortune
betinget program uden exit(rand())
.
Kan du forklare? hvad er der galt her?
Lyrisk-historisk digression
Jeg stiftede bekendtskab med denne Heisenbug for et kvart århundrede siden. Så for gatewayen i FaxNET var det nødvendigt at oprette flere hjælpeprogrammer via
Min tidligere erfaring med at håndtere fejl i sendmail og uucp/uupc tilføjede til min omhu i "grundig fejlhåndtering." Det nytter ikke noget at dykke ned i detaljerne i den historie, men jeg kæmpede med denne Heisenbug i to uger i 10-14 timer. Derfor blev det husket, og i går var denne gamle kending forbi på besøg igen.
TL;DR Svar
Hjælpeprogram head
kan lukke kanalen fra fortune
på én gang så snart han læser den første linje. Hvis fortune
udsender mere end én linje, derefter det tilsvarende opkald write()
vil returnere en fejl eller rapportere, at der udlæses færre bytes end anmodet. Til gengæld skrevet med omhyggelig fejlhåndtering fortune
har ret til at afspejle denne situation i sin exitstatus. Så på grund af installation set -o pipefail
vil arbejde || echo "Вы проиграли"
.
Imidlertid head
når det måske ikke i tide lukke før fortune
afslutter udlæsningen af data. Så vil det virke && echo "Повезло!"
.
I en af mine i dag GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
Oversat til menneske
Det er almindeligt her for --version
den spørger, hvem han er, og hvis muligheden ikke understøttes, så indsættes en stub "Brug venligst GCC eller CLANG kompatibel compiler".
lignende
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Helt ærligt genkendte jeg ikke straks min gamle "bekendt". Desuden er projektet allerede blevet testet mange gange på Elbrus og under en masse forskellige distributioner, herunder Alt. Med forskellige compilere, versioner af GNU Make og bash. Derfor ville jeg ikke se min fejl her.
Når man forsøgte at reproducere problemet og/eller forstå, hvad der foregik, begyndte der at ske flere mærkelige ting.
Kommandolinjestavelse:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
I ny og næ ville det producere ekstra tekst, så ikke... Ofte ville en af mulighederne holde sig ret længe, men hvis du skød længere, fik du altid begge dele!
Naturligvis strace
Forresten, som enhver selvrespekt strace
foretrækker ikke at reproducere.
Så hvad sker der?
- Hjælpeprogram
head
har ret (eller rettere, er endda tvunget) til at lukke den kanal, der læses, så snart den læser det ønskede antal linjer. - Den datagenererende programskriver (i dette tilfælde
cc
) kan udskrive flere linjer og gratis gøre dette gennem flere opkaldwrite()
. - Hvis læseren vil have tid til at lukke kanalen på sin side inden afslutningen af optagelsen på forfatterens side, så vil skribenten modtage en fejl.
- Forfatter program berettiget både ignorerer kanal skrivefejlen og afspejler den i din færdiggørelseskode.
- På grund af installation
set -o pipefail
pipeline-fuldførelseskoden vil være ikke-nul (fejl), hvis resultatet er ikke-nul fra mindst ét element, og så vil det fungere|| echo "Please use GCC or CLANG compatible compiler"
.
Der kan være variationer afhængigt af, hvordan forfatterprogrammet arbejder med signaler. Programmet kan f.eks. afslutte unormalt (med automatisk generering af en ikke-nul/fejl afslutningsstatus), eller write()
vil returnere resultatet af at skrive færre bytes end anmodet og indstillet errno = EPIPE
.
Hvem har skylden?
I det beskrevne tilfælde lidt af hvert. Fejlhåndtering i cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) er ikke overflødig. I mange tilfælde er det bedre at tage fejl af forsigtighed, selvom dette afslører pludselige fejl i en kedelplade designet til traditionel adfærd.
Hvad skal jeg gøre?
Forkert:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
korrigere:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Her vil tidlig lukning af et rør blive håndteret af kommandofortolkeren ved servicering af den indlejrede rørledning ("indenfor" parentesen). Følgelig, hvis
fortune
vil rapportere en fejl skriftligt til en lukket kanal i status, derefter output|| echo "Ошибка"
det kommer ingen vegne, da kanalen allerede er lukket. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Her er værktøjet
cat
fungerer som en dæmper, fordi den ignorerer fejlenEPIPE
ved tilbagetrækning. Dette er nok for nu konklusionfortune
lille (flere linjer) og passer i kanalbufferen (fra 512 bytes til ≈64K, i de fleste OS ≥4K). Ellers kan problemet vende tilbage.
Sådan behandles korrekt EPIPE
og andre optagelsesfejl?
Der er ingen enkelt rigtig løsning, men der er enkle anbefalinger:
EPIPE
påkrævet skal behandles (og afspejles i exit-status), når der udlæses data, der kræver integritet. For eksempel under driften af arkiveringsprogrammer eller backup-værktøjer.EPIPE
bedre at ignorere ved visning af information og hjælpemeddelelser. For eksempel ved visning af information om muligheder--help
eller--version
.- Hvis koden, der udvikles, kan bruges i en pipeline før
| head
derefterEPIPE
Det er bedre at ignorere, ellers er det bedre at behandle og reflektere i exitstatus.
Jeg vil gerne benytte lejligheden til at udtrykke min taknemmelighed over for holdene
Fortsæt med det Camarades, op
Tak
KDPV fra
Kilde: www.habr.com