Как заставить bash прервать выполнение скрипта при синтаксической ошибке?

15

Чтобы быть в безопасности, я бы хотел, чтобы bash прервал выполнение скрипта, если он обнаружит синтаксическую ошибку.

К моему удивлению, я не могу этого достичь. ( set -eнедостаточно). Пример:

#!/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!'

Результат (bash-3.2.39 или 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!
$ 

Ну, мы не можем проверять $?после каждого оператора, чтобы перехватить синтаксические ошибки.

(Я ожидал такого безопасного поведения от разумного языка программирования ... возможно, об этом нужно сообщать как об ошибке / желании заразить разработчиков)

Больше экспериментов

if не имеет значения.

Удаление 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!'

Результат:

$ ./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!
$ 

Возможно, это связано с упражнением 2 из http://mywiki.wooledge.org/BashFAQ/105 и связано с чем-то (( )). Но я считаю, что все еще нецелесообразно продолжать выполнять синтаксическую ошибку.

Нет, (( ))не имеет значения!

Он ведет себя плохо даже без арифметического теста! Простой, простой скрипт:

#!/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!'

Результат:

$ ./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 - Иван Захарящев
источник
set -eнедостаточно, потому что ваша синтаксическая ошибка в ifутверждении. В любом другом месте следует прервать сценарий.
Иордания
@jordanm Хорошо, это может быть объяснением, почему set -eне сработало. Но мой вопрос все еще имеет смысл. Можно ли отменить любую синтаксическую ошибку?
imz - Иван Захарьящев
@jordanm Удалено "если"; не делает различий (обновил мой вопрос).
imz - Иван Захарящев

Ответы:

9

Заключение целиком в функцию, похоже, помогает:

#!/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 "$@"

Результат:

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

Хотя я понятия не имею, почему - может быть, кто-то еще может объяснить?

ahilsend
источник
2
Теперь определение вашей функции анализируется и оценивается, и оно не выполняется.
трипл
Отличное решение! Кстати, в этом случае она также не прерывает всю программу. Я добавил echo 'Bad2: has not aborted the execution after bad main!'в качестве последнего к вашему примеру, и вывод: $ LC_ALL = C ./sh-on-syntax-err ./sh-on-syntax-err: строка 6: #: синтаксическая ошибка: ожидаемый операнд ( маркер ошибки "#") Bad2: не прервал выполнение после плохого main! $
imz - Иван Захарьящев
Но мы не должны добавлять строку просто тогда, мы должны поместить все в функцию.
imz - Иван Захарящев
@tripleee Да, похоже, что синтаксический анализ функции завершился неудачей, поэтому он не завершен, но в этом случае фактически вся программа не прерывается (так что, вероятно, это не эффект exit-on-error).
imz - Иван Захарящев
6

Вы, вероятно, вводите в заблуждение относительно подлинного значения set -e. Внимательное прочтение результатов help setшоу:

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

Так -eо состоянии выхода из команд , являющихся ненулевой, а не о синтаксических ошибках в сценарии.

В общем, это считается плохой практикой set -e, поскольку все ошибки (т. Е. Все ненулевые возвраты команд) должны быть аккуратно обработаны сценарием (подумайте надёжным сценарием, а не теми, которые выходят из строя после ввода имени файла с пространство или начинается с гипена).

В зависимости от типа синтаксической ошибки, сценарий может даже не выполняться вообще. Я не достаточно разбираюсь в bash, чтобы точно сказать , какой класс синтаксических ошибок (если только они могут быть классифицированы) может привести к немедленному прерыванию работы скрипта или нет. Может быть, некоторые гуру Баш присоединятся и все прояснят.

Я только надеюсь, что разъяснил set -eзаявление!

О вашем желании:

Я ожидал такого безопасного поведения от разумного языка программирования ... возможно, об этом нужно сообщать как об ошибке / желании заразить разработчиков

Ответ определенно нет! поскольку то, что вы наблюдали ( set -eне отвечает, как вы ожидаете), на самом деле очень хорошо задокументировано.

gniourf_gniourf
источник
Я имел в виду отсутствие такой возможности, это проблема. Я не хотел сосредотачиваться на этом set -e- это просто немного близко к моим целям, поэтому он упоминается и используется здесь. Мой вопрос не о том set -e, а о безопасности bash, если он не может быть прерван из-за синтаксических ошибок. Я ищу способ сделать так, чтобы он всегда прерывался из-за синтаксических ошибок.
imz - Иван Захарящев
4

Вы можете сделать скрипт сам по себе, поставив что-то вроде

bash -n "$0"

в верхней части скрипта - после, set -eно перед любым значительным фрагментом кода.

Я должен сказать, что это не очень хорошо, но если это работает для вас, возможно, это приемлемо.

tripleee
источник
Отлично! Или без set -e: bash -n "$0" || exit
Даниэль С
0

Во-первых, (( ))in bash используется в качестве арифметических вычислений, а не в if ... используйте []для этого.

Во-вторых, ${a[#]}это странно, и именно поэтому выдает ошибки ... #не имеет никакого значения массива

Я не знаю, что вы хотите сделать с этим, но я предполагаю, что вы хотите знать количество полей, поэтому вы хотите ${#a[*]}вместо

Наконец, при сравнении целых чисел -eqрекомендуется использовать более ==(используется для строк). также ==будет работать, но -eqрекомендуется.

так ты хочешь:

if [ ${#a[*]} -eq 2 ]; then 
Игиты
источник
4
Это не правда. Обычно используется ((ключевое слово с ifключевым словом. Например, if (( 5 * $b > 53 )). Если вы стремитесь к переносимости со старыми снарядами, [[не является в целом предпочтительнее более [.
2
Да, я согласен с @Evan - [[и ((были разработаны специально как облегченные тесты, которые будут использоваться с «если» и т. Д. - в отличие от них [, они никогда не порождают подпроцесс для оценки состояния.
imz - Иван Захарящев
1
[это встроенный Bash. Старые оболочки ожидают, что это будет его собственная программа.