$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
Tutaj fortune
program warunkowy bez exit(rand())
.
Możesz wytłumaczyć? co tu jest nie tak?
Dygresja liryczno-historyczna
Po raz pierwszy zetknąłem się z tym Heisenbugiem ćwierć wieku temu. Następnie dla bramy w FaxNET konieczne było utworzenie kilku narzędzi via
Moje wcześniejsze doświadczenie w radzeniu sobie z błędami w sendmailu i uucp/uupc zwiększyło moją staranność w „dokładnej obsłudze błędów”. Nie ma sensu zagłębiać się w szczegóły tej historii, ale męczyłem się z tym Heisenbugiem przez dwa tygodnie po 10-14 godzin. Dlatego też o tym przypomniano i wczoraj ten stary znajomy odwiedził nas ponownie.
Odpowiedź TL;DR
Użyteczność head
może zamknij kanał z fortune
od razu gdy tylko przeczyta pierwszą linijkę. Jeśli fortune
wyprowadza więcej niż jedną linię, a następnie odpowiednie wywołanie write()
zwróci błąd lub zgłosi, że wyprowadzono mniej bajtów niż żądano. Z kolei napisany z ostrożną obsługą błędów fortune
ma prawo odzwierciedlić tę sytuację w swoim statusie wyjścia. Potem z powodu instalacji set -o pipefail
będzie działać || echo "Вы проиграли"
.
Jednak head
może nie zdążyć zamknąć wcześniej fortune
zakończy wysyłanie danych. Wtedy to zadziała && echo "Повезло!"
.
W jednym z moich dzisiejszych GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
Przetłumaczone na człowieka
Jest to tutaj powszechne --version
pyta, kim jest, a jeśli opcja nie jest obsługiwana, wstawiany jest kod pośredniczący „Użyj kompilatora kompatybilnego z GCC lub CLANG”.
jak
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Szczerze mówiąc, nie od razu rozpoznałem mojego starego „znajomego”. Co więcej, projekt był już wielokrotnie testowany na Elbrusie i pod wieloma różnymi dystrybucjami, w tym Alt. Z różnymi kompilatorami, wersjami GNU Make i bash. Dlatego nie chciałem widzieć tutaj mojego błędu.
Kiedy próbowałem odtworzyć problem i/lub zrozumieć, co się dzieje, zaczęły się dziać dziwniejsze rzeczy.
Zaklęcie wiersza poleceń:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
Od czasu do czasu pojawiał się dodatkowy tekst, a potem nie... Często jedna z opcji pozostawała w pamięci przez dłuższy czas, ale jeśli naciskałeś dłużej, zawsze miałeś obie!
Oczywiście strace
Nawiasem mówiąc, jak każdy szanujący się strace
woli się nie rozmnażać.
Więc co się dzieje?
- Użyteczność
head
ma prawo (a raczej jest zmuszony) zamknąć czytany kanał, gdy tylko odczyta żądaną liczbę wierszy. - Twórca programu generującego dane (w tym przypadku
cc
) może wydrukuj wiele linii i bezpłatny zrób to poprzez wiele połączeńwrite()
. - jeśli czytelnik zdąży zamknąć kanał po swojej stronie przed zakończeniem nagrywania po stronie piszącego, wówczas piszący otrzyma błąd.
- Program pisarza jest uprawniony do oba ignorują błąd zapisu kanału i odzwierciedlają go w kodzie zakończenia.
- Ze względu na instalację
set -o pipefail
kod zakończenia potoku będzie niezerowy (błędny), jeśli wynik będzie różny od zera przynajmniej w jednym elemencie i wtedy zadziała|| echo "Please use GCC or CLANG compatible compiler"
.
Mogą występować różnice w zależności od sposobu, w jaki program piszący współpracuje z sygnałami. Na przykład program może zakończyć się nieprawidłowo (z automatycznym wygenerowaniem niezerowego statusu zakończenia/błędu) lub write()
zwróci wynik zapisania mniejszej liczby bajtów niż żądano i ustawiono errno = EPIPE
.
Kto jest winny?
W opisywanym przypadku trochę wszystkiego. Błąd podczas obsługi cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) nie jest zbędny. W wielu przypadkach lepiej jest zachować ostrożność, chociaż ujawnia to nagłe błędy w szablonie zaprojektowanym dla tradycyjnego zachowania.
Co robić?
Źle:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
Poprawnie:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
W tym przypadku wcześniejsze zamknięcie potoku będzie obsługiwane przez interpreter poleceń podczas obsługi zagnieżdżonego potoku („w” nawiasach). Odpowiednio, jeśli
fortune
zgłosi błąd zapisu do zamkniętego kanału w statusie, a następnie na wyjściu|| echo "Ошибка"
nigdzie nie dotrze, ponieważ kanał jest już zamknięty. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Oto narzędzie
cat
działa jak tłumik, ponieważ ignoruje błądEPIPE
po wycofaniu. To tyle na razie, podsumowującfortune
mały (kilka linii) i mieści się w buforze kanału (od 512 bajtów do ≈64K, w większości systemów operacyjnych ≥4K). W przeciwnym razie problem może powrócić.
Jak prawidłowo przetwarzać EPIPE
i inne błędy nagrywania?
Nie ma jednego właściwego rozwiązania, ale istnieją proste zalecenia:
EPIPE
wymagane musi zostać przetworzony (i odzwierciedlone w statusie wyjścia) podczas wysyłania danych wymagających integralności. Na przykład podczas działania archiwizatorów lub narzędzi do tworzenia kopii zapasowych.EPIPE
lepiej zignorować podczas wyświetlania informacji i komunikatów pomocniczych. Na przykład podczas wyświetlania informacji o opcjach--help
lub--version
.- Jeśli opracowywany kod może zostać wcześniej użyty w potoku
| head
następnieEPIPE
Lepiej zignorować, w przeciwnym razie lepiej przetworzyć i odzwierciedlić status wyjścia.
Chciałbym skorzystać z okazji, aby wyrazić wdzięczność zespołom
Tak trzymać, Camarades, tak trzymać
Dzięki
KDPV z
Źródło: www.habr.com