$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
Ovdje fortune
uvjetni program bez exit(rand())
.
Možeš li objasniti? što ovdje nije u redu?
Lirsko-povijesna digresija
S tim sam se Heisenbugom prvi put upoznao prije četvrt stoljeća. Tada je za gateway u FaxNET-u bilo potrebno stvoriti nekoliko pomoćnih programa putem
Moje prethodno iskustvo rješavanja grešaka u sendmailu i uucp/uupc-u doprinijelo je mojoj marljivosti u "temeljitom rješavanju grešaka". Nema smisla ulaziti u detalje te priče, ali s ovim Heisenbugom sam se mučio dva tjedna po 10-14 sati. Stoga je ostalo zapamćeno, a jučer je ovaj stari znanac ponovno svratio u goste.
TL;DR Odgovor
Korisnost head
može zatvoriti kanal iz fortune
сразу čim pročita prvi redak. Ako fortune
ispisuje više od jedne linije, zatim odgovarajući poziv write()
vratit će pogrešku ili izvijestiti da je izlaz manje bajtova od traženog. Zauzvrat, napisano uz pažljivo rukovanje pogreškama fortune
ima pravo odražavati ovu situaciju u svom izlaznom statusu. Zatim zbog ugradnje set -o pipefail
će raditi || echo "Вы проиграли"
.
Međutim, head
možda neće stići na vrijeme zatvori prije fortune
će završiti ispis podataka. Onda će uspjeti && echo "Повезло!"
.
U jednoj 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
Ovdje je uobičajeno za --version
pita se tko je on, a ako opcija nije podržana, ubacuje se zaglavak "Molimo koristite GCC ili CLANG kompatibilni kompajler".
kao
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Iskreno govoreći, nisam odmah prepoznao svog starog "poznanika". Štoviše, projekt je već testiran mnogo puta na Elbrusu i pod puno različitih distribucija, uključujući Alt. S raznim kompajlerima, verzijama GNU Make i bash. Stoga nisam želio vidjeti svoju grešku ovdje.
Prilikom pokušaja reproduciranja problema i/ili razumijevanja što se događa, počele su se događati još čudne stvari.
Čarolija naredbenog retka:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
Tu i tamo proizveo bi dodatni tekst, pa ne... Često bi se jedna od opcija zadržala dosta dugo, ali ako ste dulje bockali, uvijek ste dobili obje!
Naravno, strace
Usput, kao i svaki koji poštuje sebe strace
radije se ne razmnožava.
I što ima?
- Korisnost
head
ima pravo (ili bolje rečeno, čak je prisiljen) zatvoriti kanal koji se čita čim pročita traženi broj redaka. - Autor programa za generiranje podataka (u ovom slučaju
cc
) može ispisati više redaka i besplatno učiniti to kroz više pozivawrite()
. - Ako čitatelj će imati vremena zatvoriti kanal na svojoj strani prije kraja snimanja na strani pisca, tada će pisac primiti pogrešku.
- Program Writer pravo na oba zanemaruju pogrešku pisanja kanala i odražavaju je u vašem kodu za dovršetak.
- Zbog ugradnje
set -o pipefail
kod završetka cjevovoda bit će 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 prekinuti neuobičajeno (s automatskim generiranjem statusa prekida koji nije nula/pogreška), ili write()
će vratiti rezultat zapisivanja manjeg broja bajtova nego što je traženo i postavljeno errno = EPIPE
.
Tko je kriv?
U opisanom slučaju od svega po malo. Obrada pogreške u cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nije blagoglagoljiv. U mnogim slučajevima bolje je pogriješiti na strani opreza, iako to otkriva iznenadne nedostatke u šabloni dizajniranoj za tradicionalno ponašanje.
Što učiniti?
Pogrešno:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
ispraviti:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Ovdje će ranim zatvaranjem cijevi upravljati tumač naredbi prilikom servisiranja ugniježđenog cjevovoda ("unutar" zagrada). Prema tome, ako
fortune
prijavit će pogrešku u pisanom obliku zatvorenom kanalu u statusu, zatim izlaz|| echo "Ошибка"
neće nikamo stići, jer je kanal već zatvoren. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Evo korisnosti
cat
djeluje kao prigušivač jer ignorira pogreškuEPIPE
pri povlačenju. Ovo je dovoljno za sada zaključakfortune
mali (nekoliko redaka) i stane u međuspremnik kanala (od 512 bajtova do ≈64K, u većini OS-a ≥4K). Inače se problem može vratiti.
Kako pravilno obraditi EPIPE
i druge greške snimanja?
Ne postoji jedinstveno pravo rješenje, ali postoje jednostavne preporuke:
EPIPE
potreban mora se obraditi (i odražava se u statusu izlaza) prilikom ispisa podataka koji zahtijevaju cjelovitost. Na primjer, tijekom rada arhivara ili uslužnih programa za sigurnosno kopiranje.EPIPE
bolje ignorirati 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
tadaEPIPE
Bolje je ignorirati, inače je bolje obraditi i odražavati u statusu izlaza.
Želio bih iskoristiti ovu priliku da izrazim svoju zahvalnost timovima
Samo tako nastavi Camarades, gore
Hvala
KDPV iz
Izvor: www.habr.com