Co robi „set –e” i dlaczego można go uznać za niebezpieczny?

147

To pytanie pojawiło się w quizie przed wywiadem i doprowadza mnie do szału. Czy ktoś może odpowiedzieć na to i uspokoić mnie? Quiz nie ma odniesienia do konkretnej powłoki, ale opis zadania dotyczy unixa sa. znowu pytanie jest po prostu ...

Co robi „set –e” i dlaczego można go uznać za niebezpieczny?

egorgry
źródło
Oto powiązany wątek: stackoverflow.com/questions/64786/error-handling-in-bash/…
Stefan Lasiewski

Odpowiedzi:

154

set -e powoduje zamknięcie powłoki, jeśli jakakolwiek komenda lub potok zwraca status niezerowy.

Odpowiedź, której prawdopodobnie szukał ankieter, to:

Podczas tworzenia skryptów init.d niebezpieczne byłoby użycie „set -e”:

From http://www.debian.org/doc/debian-policy/ch-opersys.html 9.3.2 -

Uważaj na użycie zestawu -e w skryptach init.d. Pisanie poprawnych skryptów init.d wymaga akceptacji różnych statusów wyjścia błędów, gdy demony już działają lub są zatrzymane bez przerywania skryptu init.d, a wspólne biblioteki funkcji init.d nie są bezpieczne do wywoływania z ustawionym zestawem -e. W przypadku skryptów init.d często łatwiej jest nie używać zestawu -e i zamiast tego sprawdzać osobno wynik każdego polecenia.

To ważne pytanie z punktu widzenia ankietera, ponieważ ocenia kandydatów pod kątem znajomości skryptów i automatyzacji na poziomie serwera

Bogaty
źródło
Słuszna uwaga. zatrzymałoby to proces rozruchu nad czymś, co technicznie może być błędem, ale nie powinno spowodować zatrzymania całego skryptu
Matt
Chociaż zgadzam się z stackoverflow.com/questions/78497/... , myślę, że ten styl dobrze pasuje do bash do krótkich sprawdzeń init i rejestrowania lub łatania małp w innym środowisku przed wykonaniem docelowego obciążenia. Używamy tej samej zasady dla naszego obrazu dokera skrypty inicjujące.
joshperry
33

Od bash(1):

          -e      Exit immediately if a pipeline (which may consist  of  a
                  single  simple command),  a subshell command enclosed in
                  parentheses, or one of the commands executed as part  of
                  a  command  list  enclosed  by braces (see SHELL GRAMMAR
                  above) exits with a non-zero status.  The shell does not
                  exit  if  the  command that fails is part of the command
                  list immediately following a  while  or  until  keyword,
                  part  of  the  test  following  the  if or elif reserved
                  words, part of any command executed in a && or  ││  list
                  except  the  command  following  the final && or ││, any
                  command in a pipeline but the last, or if the  command’s
                  return  value  is being inverted with !.  A trap on ERR,
                  if set, is executed before the shell exits.  This option
                  applies to the shell environment and each subshell envi-
                  ronment separately (see  COMMAND  EXECUTION  ENVIRONMENT
                  above), and may cause subshells to exit before executing
                  all the commands in the subshell.

Niestety nie jestem wystarczająco kreatywny, aby wymyślić, dlaczego byłoby to niebezpieczne, poza tym, że „reszta skryptu nie zostanie wykonana” lub „być może może maskować prawdziwe problemy”.

Ignacio Vazquez-Abrams
źródło
1
maskuj prawdziwe / inne problemy, tak, chyba widziałem, że ... próbowałem wymyślić przykład, że jest to również niebezpieczne ...
cpbills
Jest strona dla set! Zawsze kończę na builtin(1). Dzięki.
Jared Beck
skąd mam wiedzieć, że setnależy znaleźć dokumentację man 1 bash? i jak ktokolwiek mógłby znaleźć -eopcję setsłowa kluczowego na tak ogromnej stronie podręcznika? nie możesz po prostu /-etutaj szukać
Blauhirn
@Blauhirn usinghelp set
Blauhirn 13.03.18
19

Należy zauważyć, że set -emożna go włączać i wyłączać dla różnych sekcji skryptu. Nie musi być włączony podczas wykonywania całego skryptu. Może być nawet warunkowo włączony. To powiedziawszy, nigdy go nie używam, ponieważ wykonuję własną obsługę błędów (lub nie).

some code
set -e     # same as set -o errexit
more code  # exit on non-zero status
set +e     # same as set +o errexit
more code  # no exit on non-zero status

Na uwagę zasługuje również sekcja strony podręcznika użytkownika Bash na trapkomendzie, która opisuje również sposób set -edziałania w określonych okolicznościach.

Pułapka ERR nie jest wykonywana, jeśli nieudane polecenie znajduje się na liście poleceń bezpośrednio po słowie kluczowym chwilę lub do, część testu w instrukcji if, część polecenia wykonanego na liście && lub ⎪⎪, lub jeśli polecenie zwracana wartość jest odwracana przez! Są to te same warunki, których przestrzega opcja errexit.

Istnieją więc pewne warunki, w których stan niezerowy nie spowoduje wyjścia.

Wydaje mi się, że niebezpieczeństwem jest niezrozumienie, kiedy set -ewchodzi w grę, a kiedy nie, i poleganie na nim niepoprawnie przy niewłaściwych założeniach.

Zobacz także BashFAQ / 105 Dlaczego nie ustawiam -e (lub ustawia -o errexit lub pułapkę ERR) zgodnie z oczekiwaniami?

Dennis Williamson
źródło
Odchodząc nieco od pytania, ta odpowiedź jest dokładnie tym, czego szukam: wyłączam ją tylko na niewielką część skryptu!
Stephan Bijzitter
13

Pamiętaj, że jest to quiz na rozmowę kwalifikacyjną. Pytania mogły zostać napisane przez obecnych pracowników i mogą być błędne. Niekoniecznie jest to złe i wszyscy popełniają błędy, a pytania podczas wywiadu często siedzą w ciemnym kącie bez recenzji i pojawiają się tylko podczas wywiadu.

Jest całkiem możliwe, że „zestaw -e” nie robi nic, co uważalibyśmy za „niebezpieczne”. Ale autor tego pytania może błędnie uwierzyć, że „zestaw -e” jest niebezpieczny z powodu własnej ignorancji lub stronniczości. Może napisali błędny skrypt powłoki, bombardowali go okropnie i błędnie myśleli, że winny był „zestaw -e”, podczas gdy w rzeczywistości zaniedbali napisania właściwego sprawdzania błędów.

W ciągu ostatnich 2 lat uczestniczyłem prawdopodobnie w 40 rozmowach o pracę, a ankieterzy czasami zadają pytania, które są złe lub mają błędne odpowiedzi.

A może to podchwytliwe pytanie, które byłoby kiepskie, ale nie do końca nieoczekiwane.

A może to dobre wytłumaczenie: http://www.mail-archive.com/[email protected]/msg473314.html

Stefan Lasiewski
źródło
1
+1 Jestem na tej samej łodzi, a wiele wywiadów „technicznych”, z którymi miałem do czynienia, było co najmniej nieco błędnych.
cpbills
1
łał. Dobre znalezisko na liście debian. +1 za ignorancję pytań podczas rozmowy kwalifikacyjnej. Pamiętam, jak raz spierałem się z odpowiedzią netback, ponieważ byłem w 100% pewien, że miałem rację. Powiedzieli, że nie. Poszedłem do domu i googlowałem, miałem rację.
egorgry
9

set -e nakazuje bashowi, w skrypcie, aby zakończył działanie, gdy cokolwiek zwróci niezerową wartość zwracaną.

widziałem, jak to byłoby denerwujące i buggy, nie jestem pewien co do niebezpieczeństwa, chyba że otworzysz uprawnienia do czegoś, a zanim będziesz mógł je ponownie ograniczyć, twój skrypt umarł.

cpbills
źródło
To interesujące. Nie myślałem o uprawnieniach otwieranych w skrypcie, który umiera przedwcześnie, ale widziałem, że jest to uważane za niebezpieczne. Zabawną częścią tego quizu jest to, że nie możesz używać żadnych materiałów referencyjnych, takich jak człowiek czy Google, a jeśli nie możesz w pełni odpowiedzieć, nie odpowiadaj w ogóle.
egorgry
6
to po prostu głupie, chciałbym ponownie rozważyć tego pracodawcę ... żartuję (tak jakby). dobrze jest mieć solidną bazę wiedzy, ale połowa pracy IT to wiedza / gdzie / znalezienie informacji ... mam nadzieję, że byli wystarczająco inteligentni, aby wziąć to pod uwagę przy ocenie kandydatów. na marginesie, widzę / no / use, bo set -ew rzeczywistości dla mnie mówi się o lenistwie, aby zignorować sprawdzanie błędów w swoich skryptach ... więc miej to na uwadze, również z tym pracodawcą ... jeśli go używają bardzo, jak wyglądają ich skrypty, które nieuchronnie będziesz musiał utrzymywać ...
cpbills
doskonały punkt @ rachunki. Nie widzę też sensu dla zestawu -e w skrypcie i prawdopodobnie dlatego tak mnie zaskoczył. Chciałbym opublikować wszystkie pytania, ponieważ niektóre są naprawdę dobre, a niektóre są naprawdę zwariowane i losowe, jak to.
egorgry
Widziałem to w wielu zadaniach systemowego crona ...
Andrew
8

set -ekończy skrypt, jeśli napotkany zostanie niezerowy kod wyjścia, poza pewnymi warunkami. Podsumowując niebezpieczeństwa związane z jego użyciem w kilku słowach: nie zachowuje się tak, jak ludzie myślą.

Moim zdaniem należy go traktować jako niebezpieczny hack, który nadal istnieje wyłącznie w celach kompatybilności. set -eOświadczenie nie włącza powłokę z języka, który wykorzystuje kody błędów na język, który wykorzystuje przepływ sterowania wyjątku podobny, to jedynie słabo próbuje naśladować to zachowanie.

Greg Wooledge ma wiele do powiedzenia na temat zagrożeń związanych z set -e:

W drugim linku znajdują się różne przykłady nieintuicyjnego i nieprzewidywalnego zachowania set -e.

Niektóre przykłady nieintuicyjnego zachowania set -e(niektóre z powyższego linku wiki):

  • ustaw -e
    x = 0
    niech x ++
    echo „x to $ x”
    Powyższe spowoduje przedwczesne zamknięcie skryptu powłoki, ponieważ let x++zwraca 0, które jest traktowane przez letsłowo kluczowe jako wartość fałszowania i zamieniane w niezerowy kod wyjścia. set -ezauważa to i cicho kończy skrypt.
  • ustaw -e
    [-d / opt / foo] && echo „Ostrzeżenie: foo jest już zainstalowany. Zastąpi”. > I 2
    echo „Instalowanie foo ...”
    

    Powyższe działa zgodnie z oczekiwaniami, drukując ostrzeżenie, jeśli /opt/foojuż istnieje.

    ustaw -e
    check_previous_install () {
        [-d / opt / foo] && echo „Ostrzeżenie: foo jest już zainstalowany. Zastąpi”. > I 2
    }
    
    check_previous_install
    echo „Instalowanie foo ...”
    

    Powyższe, pomimo że jedyną różnicą jest to, że pojedyncza linia została przekształcona w funkcję, zakończy się, jeśli /opt/foonie istnieje. Wynika to z faktu, że fakt, że działał pierwotnie, stanowi wyjątek od set -ezachowania. Gdy a && bzwraca wartość niezerową, jest ignorowana przez set -e. Jednak teraz, gdy jest to funkcja, kod wyjścia funkcji jest równy kodowi wyjścia tej komendy, a funkcja zwracająca wartość niezerową po cichu zakończy skrypt.

  • ustaw -e
    IFS = $ '\ n' read -d '' -r -a config_vars <config
    

    Powyższe przeczyta tablicę config_varsz pliku config. Jak autor może chcieć, kończy się z błędem, jeśli go configbrakuje. Ponieważ autor może nie zamierzać, po cichu kończy, jeśli confignie kończy się na nowej linii. Gdyby set -enie były tu użyte, wówczas config_varszawierałyby wszystkie wiersze pliku, niezależnie od tego, czy zakończyły się one nową linią.

    Użytkownicy Sublime Text (i innych edytorów tekstu, które nieprawidłowo obsługują znaki nowej linii), wystrzegają się.

  • ustaw -e
    
    should_audit_user () {
        grupy grup lokalnych = „$ (grupy„ $ 1 ”)”
        dla grupy w $ grupach; zrobić
            if [„$ group” = audyt]; następnie zwróć 0; fi
        gotowy
        zwrot 1
    }
    
    if should_audit_user "$ user"; następnie
        logger „Blah”
    fi
    

    Tutaj autor może zasadnie oczekiwać, że jeśli z jakiegoś powodu użytkownik $usernie istnieje, groupspolecenie zakończy się niepowodzeniem, a skrypt zostanie zakończony, a użytkownik nie będzie mógł wykonać jakiegoś zadania bez kontroli. Jednak w tym przypadku set -ewypowiedzenie nigdy nie obowiązuje. Jeśli $userz jakiegoś powodu nie można go znaleźć, zamiast zakończyć skrypt, should_audit_userfunkcja zwróci niepoprawne dane, jakby set -enie działało.

    Dotyczy to każdej funkcji wywoływanej z części warunku ifinstrukcji, bez względu na to, jak głęboko jest zagnieżdżona, bez względu na to, gdzie jest zdefiniowana, bez względu na to, czy uruchomisz set -eją ponownie. Użycie ifw dowolnym momencie całkowicie wyłącza efekt do set -emomentu pełnego wykonania bloku warunków. Jeśli autor nie jest świadomy tego pułapki lub nie zna całego stosu wywołań we wszystkich możliwych sytuacjach, w których można wywołać funkcję, wówczas napisze kod błędu i dostarczone fałszywe poczucie bezpieczeństwa set -ebędzie przynajmniej częściowo winić.

    Nawet jeśli autor jest w pełni świadomy tego pułapki, obejście polega na napisaniu kodu w taki sam sposób, w jaki napisano by go bez set -e, skutecznie czyniąc ten przełącznik mniej niż bezużytecznym; autor musi nie tylko napisać ręczny kod obsługi błędów, jakby set -enie obowiązywał, ale obecność set -emoże go oszukać, że nie muszą.

Niektóre dalsze wady set -e:

  • Zachęca niechlujny kod. Programy obsługi błędów są całkowicie zapomniane, mając nadzieję, że cokolwiek się nie powiedzie, zgłosi błąd w rozsądny sposób. Jednak w przypadku przykładów takich jak let x++powyżej nie ma to miejsca. Jeśli skrypt niespodziewanie umiera nieoczekiwanie, zwykle jest cichy, co utrudnia debugowanie. Jeśli skrypt nie umiera, a spodziewałeś się tego (patrz poprzedni punkt), masz na rękach bardziej subtelny i podstępny błąd.
  • Prowadzi ludzi do fałszywego poczucia bezpieczeństwa. Zobacz ponownie punkt- ifwarunek-punkt.
  • Miejsca, w których kończy się powłoka, nie są spójne między powłokami lub wersjami powłok. Możliwe jest przypadkowe napisanie skryptu, który zachowuje się inaczej w starszej wersji basha, ponieważ zachowano go w set -eróżnych wersjach.

set -ejest kwestią sporną, a niektórzy zdają sobie sprawę z otaczających go problemów, którzy go zalecają, podczas gdy inni zalecają ostrożność, gdy jest aktywny, aby poznać pułapki. Jest wielu początkujących skryptów powłoki, którzy polecają set -ewszystkie skrypty jako uniwersalne rozwiązanie w przypadku błędów, ale w rzeczywistości tak nie działa.

set -e nie zastąpi edukacji.

Score_Under
źródło
2

Powiedziałbym, że to niebezpieczne, ponieważ nie kontrolujesz już, jak przebiega twój skrypt. Skrypt może zostać zakończony, o ile dowolne z poleceń wywoływanych przez skrypt zwraca wartość niezerową. Więc wszystko, co musisz zrobić, to zrobić coś, co zmieni zachowanie lub dane wyjściowe któregokolwiek ze składników, i możesz zakończyć główny skrypt. Może to być bardziej problem ze stylem, ale na pewno ma konsekwencje. Co jeśli ten twój główny skrypt powinien ustawić flagę, a tak się nie stało, ponieważ zakończył się wcześnie? Skończyłbyś z resztą systemu, jeśli zakłada, że ​​flaga powinna tam być, lub pracuje z nieoczekiwaną wartością domyślną lub starą.

Marcin
źródło
Całkowicie zgadzam się z tą odpowiedzią. Nie jest to z natury niebezpieczne, ale jest okazją do niespodziewanego wczesnego wyjścia.
pboin
1
To mi przypomniało, że mój C ++ prof zawsze pobierał punkty z moich zadań za brak jednego punktu wejścia / pojedynczego punktu wyjścia w programie. Myślałem, że to była czysta „zasada”, ale ten zestaw - biznes zdecydowanie demonstruje, w jaki sposób i dlaczego wszystko może wymknąć się spod kontroli. Ostatecznie programy dotyczą kontroli i determinizmu, a przy przedwczesnym zakończeniu rezygnujesz z obu.
Marcin
0

Jako bardziej konkretny przykład odpowiedzi @ Marcina, która osobiście mnie ugryzła, wyobraź sobie, że rm foo/bar.txtgdzieś w twoim skrypcie była linia. Zwykle nic wielkiego, jeśli foo/bar.txttak naprawdę nie istnieje. Jednak z set -eobecnym teraz twój skrypt skończy się tam wcześnie! Ups

Suan
źródło