$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
Hier fortune
voorwaardelijk programma zonder exit(rand())
.
Kun je uitleggen? wat is hier mis?
Lyrisch-historische uitweiding
Een kwart eeuw geleden maakte ik voor het eerst kennis met deze Heisenbug. Vervolgens was het voor de gateway in FaxNET nodig om verschillende hulpprogramma's via te maken
Mijn eerdere ervaring met het omgaan met bugs in sendmail en uucp/uupc droeg bij aan mijn ijver in “grondige foutafhandeling.” Het heeft geen zin om in de details van dat verhaal te duiken, maar ik worstelde twee weken lang met deze Heisenbug gedurende 10-14 uur. Daarom werd het herdacht, en gisteren kwam deze oude bekende langs om opnieuw op bezoek te komen.
TL; DR Antwoord
Nut head
kan sluit het kanaal af fortune
onmiddelijk zodra hij de eerste regel leest. Als fortune
meer dan één lijn uitvoert, en vervolgens de bijbehorende oproep write()
zal een fout retourneren of rapporteren dat er minder bytes worden uitgevoerd dan gevraagd. Op zijn beurt geschreven met zorgvuldige foutafhandeling fortune
heeft het recht om deze situatie weer te geven in zijn exitstatus. Dan vanwege installatie set -o pipefail
zal werken || echo "Вы проиграли"
.
Echter head
lukt het misschien niet op tijd eerder sluiten fortune
zal het uitvoeren van gegevens voltooien. Dan zal het werken && echo "Повезло!"
.
In een van mijn vandaag GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
Vertaald in de mens
Het is hier gebruikelijk --version
er wordt gevraagd wie hij is, en als de optie niet wordt ondersteund, wordt er een stub ingevoegd "Gebruik een GCC- of CLANG-compatibele compiler".
zoals
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Eerlijk gezegd herkende ik mijn oude ‘kennis’ niet meteen. Bovendien is het project al vele malen getest op Elbrus en onder veel verschillende distributies, waaronder Alt. Met verschillende compilers, versies van GNU Make en bash. Daarom wilde ik mijn fout hier niet zien.
Toen ik probeerde het probleem te reproduceren en/of te begrijpen wat er aan de hand was, begonnen er meer vreemde dingen te gebeuren.
Opdrachtregelspelling:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
Zo nu en dan leverde het extra tekst op, en dan weer niet... Vaak bleef een van de opties behoorlijk lang hangen, maar als je langer prikte, kreeg je altijd beide!
Natuurlijk strace
Trouwens, zoals elke zichzelf respecterende strace
geeft er de voorkeur aan zich niet voort te planten.
Dus wat is er aan de hand?
- Nut
head
heeft het recht (of beter gezegd, wordt zelfs gedwongen) om het gelezen kanaal te sluiten zodra het het gevraagde aantal regels leest. - De gegevensgenererende programmaschrijver (in dit geval
cc
) kan meerdere regels afdrukken en vrij doe dit via meerdere oproepenwrite()
. - als de lezer heeft tijd om het kanaal aan zijn kant te sluiten vóór het einde van de opname aan de kant van de schrijver, waarna de schrijver een foutmelding krijgt.
- Schrijver programma gerechtigd beide negeren de kanaalschrijffout en geven deze weer in uw voltooiingscode.
- Wegens installatie
set -o pipefail
de pijplijnvoltooiingscode zal niet nul zijn (foutief) als het resultaat van ten minste één element niet nul is, en dan zal het werken|| echo "Please use GCC or CLANG compatible compiler"
.
Er kunnen variaties zijn, afhankelijk van hoe het schrijfprogramma met signalen werkt. Het programma kan bijvoorbeeld abnormaal eindigen (met automatische generatie van een niet-nul/foutbeëindigingsstatus), of write()
zal het resultaat retourneren van het schrijven van minder bytes dan gevraagd en ingesteld errno = EPIPE
.
Wie is de schuldige?
In het beschreven geval een beetje van alles. Foutafhandeling binnen cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) is niet overbodig. In veel gevallen is het beter om het zekere voor het onzekere te nemen, hoewel dit wel plotselinge gebreken aan het licht brengt in een standaard die is ontworpen voor traditioneel gedrag.
Wat te doen?
Mis:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
corrigeren:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Hier wordt het vroegtijdig sluiten van een pijpleiding afgehandeld door de opdrachtinterpreter bij het onderhouden van de geneste pijpleiding ("binnen" de haakjes). Dienovereenkomstig, als
fortune
rapporteert een fout bij het schrijven naar een gesloten kanaal in de status en vervolgens naar de uitgang|| echo "Ошибка"
het komt nergens, omdat het kanaal al gesloten is. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Hier is het hulpprogramma
cat
werkt als een demper omdat het de fout negeertEPIPE
bij intrekking. Dit is voor nu voldoende conclusiefortune
klein (meerdere regels) en past in de kanaalbuffer (van 512 bytes tot ≈64K, in de meeste besturingssystemen ≥4K). Anders kan het probleem terugkeren.
Hoe correct te verwerken EPIPE
en andere opnamefouten?
Er is niet één juiste oplossing, maar er zijn eenvoudige aanbevelingen:
EPIPE
nodig moet worden verwerkt (en weerspiegeld in de afsluitstatus) bij het uitvoeren van gegevens waarvoor integriteit vereist is. Bijvoorbeeld tijdens de werking van archiverings- of back-uphulpprogramma's.EPIPE
beter te negeren bij het weergeven van informatie en hulpberichten. Bijvoorbeeld bij het weergeven van informatie over opties--help
of--version
.- Als de code die wordt ontwikkeld eerder in een pijplijn kan worden gebruikt
| head
danEPIPE
Het is beter om te negeren, anders is het beter om te verwerken en te reflecteren in de exit-status.
Ik wil graag van deze gelegenheid gebruik maken om mijn dank uit te spreken aan de teams
Ga zo door Camarades, blijf doorgaan
Dank
KDPV van
Bron: www.habr.com