$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
Šeit fortune
nosacītā programma bez exit(rand())
.
Vai tu vari paskaidrot? kas te vainas?
Liriski vēsturiska atkāpe
Pirmo reizi ar šo Heisenbugu iepazinos pirms ceturtdaļgadsimta. Tad vārtejai FaxNET bija nepieciešams izveidot vairākas utilītas, izmantojot
Mana iepriekšējā pieredze darbā ar kļūdām programmā Sendmail un uucp/uupc palielināja manu centību “rūpīgā kļūdu apstrādē”. Nav jēgas ienirt šī stāsta detaļās, bet es cīnījos ar šo Heisenbugu divas nedēļas 10–14 stundas. Tāpēc atcerējās, un vakar šī senā paziņa piestāja vēlreiz ciemos.
TL;DR Atbilde
Lietderība head
var aizveriet kanālu no fortune
сразу tiklīdz viņš izlasa pirmo rindiņu. Ja fortune
izvada vairāk nekā vienu līniju, pēc tam atbilstošo zvanu write()
atgriezīs kļūdu vai ziņos, ka tiek izvadīts mazāk baitu, nekā pieprasīts. Savukārt rakstīts ar rūpīgu kļūdu apstrādi fortune
ir tiesības atspoguļot šo situāciju savā izejas statusā. Tad uzstādīšanas dēļ set -o pipefail
strādās || echo "Вы проиграли"
.
Tomēr, head
var nepaspēt laikā aizvērt pirms fortune
beigs izvadīt datus. Tad tas darbosies && echo "Повезло!"
.
Vienā no manām šodienas GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
Tulkots cilvēkā
Šeit tas ir ierasts --version
tas jautā, kas viņš ir, un, ja opcija netiek atbalstīta, tad tiek ievietots stubs "Lūdzu, izmantojiet ar GCC vai CLANG saderīgu kompilatoru".
tāpat
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Atklāti sakot, es uzreiz neatpazinu savu veco "paziņu". Turklāt projekts jau ir daudzkārt pārbaudīts uz Elbrus un daudzos dažādos izplatījumos, tostarp Alt. Ar dažādiem kompilatoriem, GNU Make un bash versijām. Tāpēc es negribēju šeit redzēt savu kļūdu.
Mēģinot atveidot problēmu un/vai saprast, kas notiek, sāka notikt dīvainākas lietas.
Komandrindas pareizrakstība:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
Ik pa brīdim radīja lieku tekstu, tad nē... Bieži vien kāds no variantiem pielipa diezgan ilgi, bet, ja pabāza ilgāk, vienmēr dabūja abus!
Protams, strace
Starp citu, kā jau jebkurš sevi cienošs strace
dod priekšroku nevairot.
Tātad, kas notiek?
- Lietderība
head
ir tiesības (pareizāk sakot, ir pat spiestas) aizvērt nolasīto kanālu, tiklīdz tas nolasa pieprasīto rindu skaitu. - Datu ģenerēšanas programmas rakstītājs (šajā gadījumā
cc
) var izdrukāt vairākas rindiņas un bezmaksas dariet to, izmantojot vairākus zvanuswrite()
. - Ja lasītājam būs laiks aizvērt kanālu savā pusē pirms ierakstīšanas beigām rakstītāja pusē, tad rakstītājs saņems kļūdu.
- Rakstnieku programma ir tiesības abi ignorē kanāla rakstīšanas kļūdu un atspoguļo to pabeigšanas kodā.
- Sakarā ar uzstādīšanu
set -o pipefail
konveijera pabeigšanas kods nebūs nulle (kļūdains), ja vismaz viena elementa rezultāts nav nulle, un tad tas darbosies|| echo "Please use GCC or CLANG compatible compiler"
.
Var būt atšķirības atkarībā no tā, kā rakstīšanas programma darbojas ar signāliem. Piemēram, programma var beigties neparasti (ar automātisku pārtraukšanas statusa, kas nav nulle/kļūda, ģenerēšanu), vai write()
atgriezīs rezultātu, rakstot mazāk baitu, nekā pieprasīts un iestatīts errno = EPIPE
.
Kurš vainīgs?
Aprakstītajā gadījumā mazliet no visa. Rīkojoties, radās kļūda cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nav lieks. Daudzos gadījumos labāk ir kļūdīties piesardzīgi, lai gan tas atklāj pēkšņus defektus tradicionālajai uzvedībai paredzētajā plāksnē.
Ko darīt?
Nepareizi:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
Pareizi:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Šeit agru caurules aizvēršanu apstrādās komandu tulks, apkalpojot ligzdoto cauruļvadu (iekavās). Attiecīgi, ja
fortune
ziņos par kļūdu rakstveidā uz slēgtu kanālu statusā, pēc tam izvadē|| echo "Ошибка"
tas nekur nenonāks, jo kanāls jau ir slēgts. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Šeit ir lietderība
cat
darbojas kā slāpētājs, jo ignorē kļūduEPIPE
pēc izstāšanās. Tagad secinājumam ar to pietiekfortune
mazs (vairākas rindas) un iekļaujas kanāla buferī (no 512 baitiem līdz ≈64K, lielākajā daļā OS ≥4K). Pretējā gadījumā problēma var atgriezties.
Kā pareizi apstrādāt EPIPE
un citas ierakstīšanas kļūdas?
Nav viena pareizā risinājuma, taču ir vienkārši ieteikumi:
EPIPE
nepieciešams ir jāapstrādā (un atspoguļots izejas statusā), izvadot datus, kuriem nepieciešama integritāte. Piemēram, arhivētāju vai rezerves utilītu darbības laikā.EPIPE
labāk ignorēt kad tiek parādīta informācija un palīgziņojumi. Piemēram, kad tiek parādīta informācija par opcijām--help
vai--version
.- Ja izstrādāto kodu var izmantot konveijerā pirms
| head
tadEPIPE
Labāk ir ignorēt, pretējā gadījumā labāk ir apstrādāt un atspoguļot izejas statusu.
Vēlos izmantot iespēju, lai izteiktu pateicību komandām
Tā turpināt, Camarades, tā
Paldies
KDPV no
Avots: www.habr.com