$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
Čia fortune
sąlyginė programa be exit(rand())
.
Ar gali paaiškinti? kas čia negerai?
Lyrinis-istorinis nukrypimas
Pirmą kartą su šiuo Heisenbug susipažinau prieš ketvirtį amžiaus. Tada FaxNET šliuzui reikėjo sukurti keletą komunalinių paslaugų per
Mano ankstesnė patirtis, susijusi su „sendmail“ ir „uucp/uupc“ klaidomis, padidino mano kruopštumą „kruopštaus klaidų tvarkymo“ srityje. Nėra prasmės gilintis į tos istorijos detales, bet aš su šiuo Heisenbug kovojau dvi savaites 10–14 valandų. Todėl buvo prisiminta, o vakar šis senas pažįstamas vėl užsuko aplankyti.
TL;DR Atsakymas
Naudingumas head
galima uždarykite kanalą nuo fortune
сразу kai tik perskaitys pirmąją eilutę. Jeigu fortune
išveda daugiau nei vieną eilutę, tada atitinkamas skambutis write()
grąžins klaidą arba praneš, kad išvedama mažiau baitų, nei reikalaujama. Savo ruožtu parašyta atsargiai elgiantis su klaidomis fortune
turi teisę šią situaciją atspindėti savo išėjimo būsenoje. Tada dėl įrengimo set -o pipefail
dirbs || echo "Вы проиграли"
.
Tačiau head
gali nespėti laiku uždaryti prieš tai fortune
baigs išvesti duomenis. Tada jis veiks && echo "Повезло!"
.
Viename iš mano šiandienos GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
Išversta į žmogų
Čia įprasta --version
klausia, kas jis toks, o jei parinktis nepalaikoma, tada įterpiamas stuburas "Prašome naudoti GCC arba CLANG suderinamą kompiliatorių".
kaip
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Atvirai kalbant, aš ne iš karto atpažinau savo seną „pažįstamą“. Be to, projektas jau daug kartų buvo išbandytas Elbrus ir daugelyje skirtingų platinimų, įskaitant Alt. Su įvairiais kompiliatoriais, GNU Make ir bash versijomis. Todėl nenorėjau čia matyti savo klaidos.
Bandant atkurti problemą ir (arba) suprasti, kas vyksta, ėmė dėtis keistesni dalykai.
Komandinės eilutės rašyba:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
Karts nuo karto sukurdavo papildomo teksto, tada ne... Dažnai vienas iš variantų užstrigdavo gana ilgai, bet jei kišdavo ilgiau, visada gaudavo abu!
Žinoma, strace
Beje, kaip ir kiekvienas save gerbiantis strace
nemėgsta daugintis.
Taigi kas vyksta?
- Naudingumas
head
turi teisę (tiksliau, net priverstas) uždaryti skaitomą kanalą, kai tik jis nuskaito pageidaujamą eilučių skaičių. - Duomenis generuojanti programa-rašytojas (šiuo atveju
cc
) galima spausdinti kelias eilutes ir Laisvas tai padaryti per kelis skambučiuswrite()
. - jei skaitytojas turės laiko uždaryti kanalą savo pusėje iki įrašymo pabaigos rašytojo pusėje, tada rašytojas gaus klaidą.
- Rašytojo programa turintis teisę abu ignoruoja kanalo rašymo klaidą ir atspindi ją užbaigimo kode.
- Dėl įrengimo
set -o pipefail
konvejerio užbaigimo kodas bus ne nulis (klaidingas), jei bent vieno elemento rezultatas yra ne nulis, ir tada jis veiks|| echo "Please use GCC or CLANG compatible compiler"
.
Gali būti skirtumų, atsižvelgiant į tai, kaip rašymo programa veikia su signalais. Pavyzdžiui, programa gali baigtis nenormaliai (automatiškai sugeneravus ne nulio / klaidos nutraukimo būseną) arba write()
grąžins rezultatą, kai bus įrašyta mažiau baitų nei prašoma ir nustatyta errno = EPIPE
.
Kas yra kaltas?
Aprašytu atveju visko po truputį. Klaida tvarkant cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nėra perteklinis. Daugeliu atvejų geriau apsisaugoti, nors tai atskleidžia staigius tradiciniam elgesiui sukurtos sistemos trūkumus.
Ką daryti?
Neteisinga:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
Teisingai:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Čia ankstyvą vamzdžio uždarymą tvarkys komandų interpretatorius, kai aptarnauja įdėtą dujotiekį („skliausteliuose“). Atitinkamai, jei
fortune
praneš apie klaidą rašant į uždarą kanalą būsenoje, tada išvestis|| echo "Ошибка"
jis niekur nepateks, nes kanalas jau uždarytas. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Čia yra naudingumas
cat
veikia kaip slopintuvas, nes nepaiso klaidosEPIPE
pasitraukus. Dabar to pakanka išvadaifortune
mažas (kelios eilutės) ir telpa į kanalo buferį (nuo 512 baitų iki ≈64K, daugumoje OS ≥4K). Priešingu atveju problema gali grįžti.
Kaip teisingai apdoroti EPIPE
ir kitų įrašymo klaidų?
Vieno teisingo sprendimo nėra, tačiau yra paprastos rekomendacijos:
EPIPE
reikia turi būti apdorotas (ir atsispindi išėjimo būsenoje), kai išvedami duomenys, kuriems reikia vientisumo. Pavyzdžiui, veikiant archyvatoriams ar atsarginėms komunalinėms programoms.EPIPE
geriau ignoruoti kai rodoma informacija ir pagalbiniai pranešimai. Pavyzdžiui, kai rodoma informacija apie parinktis--help
arba--version
.- Jei kuriamas kodas gali būti naudojamas anksčiau
| head
, TadaEPIPE
Geriau ignoruoti, kitaip geriau apdoroti ir atspindėti išėjimo būseną.
Naudodamasis proga norėčiau padėkoti komandoms
Taip ir toliau, Camarades
Ačiū
KDPV iš
Šaltinis: www.habr.com