Jak zmusić bash do przerwania wykonywania skryptu w przypadku błędu składniowego?

15

Dla pewności chciałbym bash przerwać wykonywanie skryptu, jeśli napotka błąd składniowy.

Ku mojemu zaskoczeniu nie mogę tego osiągnąć. ( set -eto za mało.) Przykład:

#!/bin/bash

# Do exit on any error:
set -e

readonly a=(1 2)

# A syntax error is here:

if (( "${a[#]}" == 2 )); then
    echo ok
else
    echo not ok
fi

echo status $?

echo 'Bad: has not aborted execution on syntax error!'

Wynik (bash-3.2.39 lub bash-3.2.51):

$ ./sh-on-syntax-err
./sh-on-syntax-err: line 10: #: syntax error: operand expected (error token is "#")
status 1
Bad: has not aborted execution on syntax error!
$ 

Nie możemy sprawdzać $?po każdej instrukcji, aby wyłapać błędy składniowe.

(Spodziewałem się takiego bezpiecznego zachowania z rozsądnego języka programowania ... być może należy to zgłosić jako błąd / chęć bashowania deweloperów)

Więcej eksperymentów

if nie robi różnicy.

Usuwanie if:

#!/bin/bash

set -e # exit on any error
readonly a=(1 2)
# A syntax error is here:
(( "${a[#]}" == 2 ))
echo status $?
echo 'Bad: has not aborted execution on syntax error!'

Wynik:

$ ./sh-on-syntax-err 
./sh-on-syntax-err: line 6: #: syntax error: operand expected (error token is "#")
status 1
Bad: has not aborted execution on syntax error!
$ 

Być może jest to związane z ćwiczeniem 2 z http://mywiki.wooledge.org/BashFAQ/105 i ma coś wspólnego z tym (( )). Ale nadal uważam, że nadal nieuzasadnione jest kontynuowanie wykonywania po błędzie składni.

Nie, (( ))nie ma znaczenia!

Zachowuje się źle nawet bez testu arytmetycznego! Po prostu prosty, podstawowy skrypt:

#!/bin/bash

set -e # exit on any error
readonly a=(1 2)
# A syntax error is here:
echo "${a[#]}"
echo status $?
echo 'Bad: has not aborted execution on syntax error!'

Wynik:

$ ./sh-on-syntax-err 
./sh-on-syntax-err: line 6: #: syntax error: operand expected (error token is "#")
status 1
Bad: has not aborted execution on syntax error!
$ 
imz - Ivan Zakharyaschev
źródło
set -enie wystarczy, ponieważ błąd składniowy znajduje się w ifinstrukcji. Gdziekolwiek indziej powinien przerwać skrypt.
jordanm
@jordanm Ok, to może być wyjaśnienie, dlaczego set -enie zadziałało. Ale moje pytanie wciąż ma sens. Czy można przerwać dowolny błąd składniowy?
imz - Ivan Zachharyaschev
@jordanm Usunięto „if”; nie robi różnicy (zaktualizowałem moje pytanie).
imz - Ivan Zachharyaschev

Odpowiedzi:

9

Zawinięcie całości w funkcję wydaje się załatwić sprawę:

#!/bin/bash -e

main () {
readonly a=(1 2)
    # A syntax error is here:
    if (( "${a[#]}" == 2 )); then
        echo ok
    else
        echo not ok
    fi
    echo status $?
    echo 'Bad: has not aborted execution on syntax error!'
}

main "$@"

Wynik:

$ ./sh-on-syntax-err 
$ ./sh-on-syntax-err line 6: #: syntax error: operand expected (error token is "#")
$ 

Chociaż nie mam pojęcia, dlaczego - może ktoś inny może to wyjaśnić?

ahilsend
źródło
2
Teraz twoja definicja funkcji jest parsowana i oceniana, i kończy się niepowodzeniem.
tripleee
Fajne rozwiązanie! BTW, w tym przypadku również nie przerywa całego programu. Dołączyłem echo 'Bad2: has not aborted the execution after bad main!'jako ostatni do twojego przykładu, a wynik jest następujący: $ LC_ALL = C ./sh-on-syntax-err ./sh-on-syntax-err: linia 6: #: błąd składni: oczekiwany operand ( token błędu to „#”) Bad2: nie przerwał wykonywania po złym main! $
imz - Ivan Zachharyaschev
Ale nie powinniśmy wtedy dodawać wiersza, powinniśmy wszystko umieścić w funkcji.
imz - Ivan Zachharyaschev
@tripleee Tak, wygląda na to, że parsowanie funkcji kończy się niepowodzeniem, więc nie jest kompletne, ale w tym przypadku cały program nie jest przerywany (więc prawdopodobnie nie jest to efekt wyjścia z błędu).
imz - Ivan Zachharyaschev
6

Prawdopodobnie wprowadzasz w błąd co do prawdziwego znaczenia set -e. Uważne czytanie wyników help setprogramów:

-e  Exit immediately if a command exits with a non-zero status.

Podobnie -ejest z niezerowym statusem zakończenia poleceń , a nie z błędami składni w skrypcie.

Ogólnie rzecz biorąc, uważa się za złą praktykę set -e, ponieważ wszystkie błędy (tj. Wszystkie niezerowe zwroty z poleceń) powinny być inteligentnie obsługiwane przez skrypt (pomyśl solidny skrypt, a nie te, które szaleją po wprowadzeniu nazwy pliku za pomocą spacja lub zaczynająca się od myślnika).

W zależności od rodzaju błędu składniowego skrypt może nawet nie zostać w ogóle wykonany. Nie mam wystarczającej wiedzy na temat bash, aby powiedzieć dokładnie, jaką klasę błędów składniowych (jeśli tylko można je sklasyfikować) może prowadzić do natychmiastowej aborcji skryptu, czy nie. Może dołączy do nich jakiś guru Bash i wszystko wyjaśni.

Mam tylko nadzieję, że wyjaśniłem to set -eoświadczenie!

O twoim życzeniu:

Spodziewałem się takiego bezpiecznego zachowania z rozsądnego języka programowania ... być może należy to zgłosić jako błąd / chęć zniesławiania programistów

Odpowiedź jest zdecydowanie nie! ponieważ to, co zaobserwowałeś ( set -enie reagujesz tak, jak się spodziewałeś) jest w rzeczywistości bardzo dobrze udokumentowane.

gniourf_gniourf
źródło
Miałem na myśli, że brak takiej funkcji jest problemem. Nie chciałem się skupiać set -e- jest to trochę blisko moich celów, dlatego jest tutaj wspomniane i wykorzystywane. Moje pytanie nie dotyczy set -e, dotyczy bezpieczeństwa bashu, jeśli nie można go przerwać w przypadku błędów sytax. Szukam sposobu, aby zawsze przerywał błędy składniowe.
imz - Ivan Zakharyaschev
4

Możesz sam sprawdzić skrypt, umieszczając coś takiego

bash -n "$0"

u góry skryptu - po, set -eale przed jakimkolwiek znaczącym fragmentem kodu.

Muszę powiedzieć, że to nie wydaje się bardzo solidne, ale jeśli to działa dla ciebie, być może jest to do przyjęcia.

potrójny
źródło
Doskonały! Lub bez set -e: bash -n "$0" || exit
Daniel S
0

Po pierwsze, (( ))bash jest używany jako obliczenia arytmetyczne, a nie do użycia w if ... użyj []do tego.

Po drugie, ${a[#]}jest dziwne i dlatego daje błędy ... #nie ma żadnego znaczenia tablicowego

Nie wiem, co chcesz z tym zrobić, ale zakładam, że chcesz znać liczbę pól, więc ${#a[*]}zamiast tego

Wreszcie, podczas porównywania liczb całkowitych, -eqzalecane jest nad ==(używane do łańcuchów). ==będzie również działać, ale -eqjest zalecane.

więc chcesz:

if [ ${#a[*]} -eq 2 ]; then 
higuita
źródło
4
To nieprawda. Często używa się ((słowa kluczowego ze ifsłowem kluczowym. Na przykład if (( 5 * $b > 53 )). O ile nie dążysz do przenośności ze starszymi pociskami, [[jest to zwykle lepsze niż [.
2
Tak, zgadzam się z @Evan - [[i ((zostały zaprojektowane specjalnie jako lekkie testy do użycia z „jeśli” itp. - w przeciwieństwie do tego [, nigdy nie spawnują podprocesu do oceny stanu.
imz - Ivan Zakharyaschev
1
[jest wbudowanym bash. Starsze powłoki oczekują jednak, że będzie to własny program.