Поиск по сайту:

Как проверить синтаксис скрипта Linux Bash перед его запуском


Ошибки и опечатки в сценариях Linux Bash могут привести к ужасным последствиям при запуске сценария. Вот несколько способов проверить синтаксис ваших скриптов еще до их запуска.

Эти надоедливые ошибки

Писать код сложно. Или, если быть более точным, написать нетривиальный код без ошибок сложно. И чем больше строк кода в программе или скрипте, тем больше вероятность того, что в нем будут ошибки.

Язык, на котором вы программируете, имеет прямое отношение к этому. Программирование на ассемблере намного сложнее, чем программирование на C, а программирование на C сложнее, чем программирование на Python. Чем более низкоуровневым является язык, на котором вы программируете, тем больше работы вам приходится выполнять самостоятельно. В Python могут быть встроенные процедуры сборки мусора, но в C и ассемблере их точно нет.

Написание сценариев оболочки Linux сопряжено с определенными трудностями. В компилируемом языке, таком как C, программа, называемая компилятором, считывает ваш исходный код — удобочитаемые инструкции, которые вы вводите в текстовый файл, — и преобразует их в двоичный исполняемый файл. Двоичный файл содержит инструкции машинного кода, которые компьютер может понять и использовать.

Компилятор сгенерирует двоичный файл только в том случае, если исходный код, который он читает и анализирует, соответствует синтаксису и другим правилам языка. Если вы напишете зарезервированное слово — одно из командных слов языка — или имя переменной неправильно, компилятор выдаст ошибку.

Например, некоторые языки требуют, чтобы вы объявляли переменную перед ее использованием, другие не так привередливы. Если язык, с которым вы работаете, требует от вас объявления переменных, но вы забываете это сделать, компилятор выдаст другое сообщение об ошибке. Какими бы раздражающими ни были эти ошибки времени компиляции, они обнаруживают множество проблем и заставляют вас их решать. Но даже если у вас есть программа, в которой нет синтаксических ошибок, это не значит, что в ней нет ошибок. Отнюдь не.

Ошибки, возникающие из-за логических ошибок , обычно намного сложнее обнаружить. Если вы скажете своей программе сложить два и три, но на самом деле хотите, чтобы она сложила два и два, вы не получите ожидаемого ответа. Но программа делает то, для чего она была написана. В составе или синтаксисе программы нет ничего плохого. Проблема в вас. Вы написали правильно построенную программу, которая не делает того, что вы хотели.

Тестирование сложно

Тщательное тестирование программы, даже простой, требует времени. Запустить его несколько раз недостаточно; вам действительно нужно протестировать все пути выполнения в вашем коде, чтобы все части кода были проверены. Если программа запрашивает ввод, вам необходимо предоставить достаточный диапазон входных значений для проверки всех условий, включая неприемлемый ввод.

Для языков более высокого уровня модульные тесты и автоматизированное тестирование помогают сделать тщательное тестирование управляемым упражнением. Итак, вопрос в том, есть ли какие-либо инструменты, которые мы можем использовать, чтобы помочь нам писать безошибочные сценарии оболочки Bash?

Ответ — да, включая саму оболочку Bash.

Использование Bash для проверки синтаксиса скрипта

Параметр Bash -n (noexec) указывает Bash прочитать сценарий и проверить его на наличие синтаксических ошибок, не запуская сценарий. В зависимости от того, для чего предназначен ваш скрипт, это может быть намного безопаснее, чем запускать его и искать проблемы.

Вот скрипт, который мы собираемся проверить. Это не сложно, в основном это набор операторов if. Он запрашивает и принимает число, представляющее месяц. Скрипт решает, к какому времени года относится месяц. Очевидно, что это не сработает, если пользователь вообще не вводит никаких данных или вводит неверные данные, например, букву вместо цифры.

#! /bin/bash

read -p "Enter a month (1 to 12): " month

# did they enter anything?
if [ -z "$month" ]
then
  echo "You must enter a number representing a month."
  exit 1
fi

# is it a valid month?
if (( "$month" < 1 || "$month" > 12)); then
  echo "The month must be a number between 1 and 12."
  exit 0
fi

# is it a Spring month?
if (( "$month" >= 3 && "$month" < 6)); then
  echo "That's a Spring month."
  exit 0
fi

# is it a Summer month?
if (( "$month" >= 6 && "$month" < 9)); then
  echo "That's a Summer month."
  exit 0
fi

# is it an Autumn month?
if (( "$month" >= 9 && "$month" < 12)); then
  echo "That's an Autumn month."
  exit 0
fi

# it must be a Winter month
echo "That's a Winter month."
exit 0

Этот раздел проверяет, ввел ли пользователь что-либо вообще. Он проверяет, не установлена ли переменная $month.

if [ -z "$month" ]
then
  echo "You must enter a number representing a month."
  exit 1
fi

В этом разделе проверяется, введено ли число от 1 до 12. Он также отлавливает неверный ввод, не являющийся цифрой, поскольку буквы и знаки пунктуации не преобразуются в числовые значения.

# is it a valid month?
if (( "$month" < 1 || "$month" > 12)); then
  echo "The month must be a number between 1 and 12."
  exit 0
fi

Все остальные предложения If проверяют, находится ли значение в переменной $month между двумя значениями. Если это так, месяц принадлежит этому сезону. Например, если месяц, введенный пользователем, равен 6, 7 или 8, это летний месяц.

# is it a Summer month?
if (( "$month" >= 6 && "$month" < 9)); then
  echo "That's a Summer month."
  exit 0
fi

Если вы хотите работать с нашими примерами, скопируйте и вставьте текст сценария в редактор и сохраните его как «seasons.sh». Затем сделайте скрипт исполняемым с помощью команды chmod:

chmod +x seasons.sh

Мы можем протестировать скрипт,

  • Вообще ничего не вводить.
  • Предоставление нечислового ввода.
  • Предоставление числового значения, выходящего за пределы диапазона от 1 до 12.
  • Предоставление числовых значений в диапазоне от 1 до 12.

Во всех случаях запускаем скрипт одной и той же командой. Единственная разница заключается в вводе, который пользователь вводит при продвижении по сценарию.

./seasons.sh

Кажется, это работает так, как ожидалось. Пусть Bash проверит синтаксис нашего скрипта. Мы делаем это, вызывая параметр -n (noexec) и передавая имя нашего скрипта.

bash -n ./seasons.sh

Это тот случай, когда «отсутствие новостей — это хорошая новость». Молча вернуть нас в командную строку — это способ Bash сказать, что все в порядке. Давайте саботируем наш скрипт и вводим ошибку.

Мы удалим then из первого предложения if.

# is it a valid month?
if (( "$month" < 1 || "$month" > 12)); # "then" has been removed
  echo "The month must be a number between 1 and 12."
  exit 0
fi

Теперь давайте запустим скрипт, сначала без, а затем с вводом от пользователя.

./seasons.sh

При первом запуске сценария пользователь не вводит значение, поэтому сценарий завершается. Раздел, который мы саботировали, так и не был достигнут. Сценарий завершается без сообщения об ошибке от Bash.

При втором запуске скрипта пользователь вводит входное значение, и первое предложение if выполняется для проверки корректности ввода пользователя. Это вызывает сообщение об ошибке от Bash.

Обратите внимание, что Bash проверяет синтаксис этого предложения и каждой второй строки кода, потому что ему безразлична логика скрипта. Пользователю не предлагается ввести число, когда Bash проверяет скрипт, потому что скрипт не запущен.

Различные возможные пути выполнения скрипта не влияют на то, как Bash проверяет синтаксис. Bash просто и методично продвигается от начала сценария к концу, проверяя синтаксис для каждой строки.

Утилита ShellCheck

Линтер, названный в честь инструмента проверки исходного кода C времен расцвета Unix, представляет собой инструмент анализа кода, используемый для обнаружения ошибок программирования, стилистических ошибок и подозрительного или сомнительного использования языка. Линтеры доступны для многих языков программирования и известны своей педантичностью. Не все, что находит линтер, является ошибкой само по себе, но все, на что он обращает ваше внимание, вероятно, заслуживает внимания.

ShellCheck — это инструмент анализа кода для сценариев оболочки. Он ведет себя как линтер для Bash.

Давайте вернем недостающее зарезервированное слово then в наш скрипт и попробуем что-нибудь еще. Мы удалим открывающую скобку «[» из самого первого предложения if.

# did they enter anything?
if -z "$month" ] # opening bracket "[" removed
then
  echo "You must enter a number representing a month."
  exit 1
fi

если мы используем Bash для проверки скрипта, он не находит проблемы.

bash -n seasons.sh
./seasons.sh

Но когда мы пытаемся запустить сценарий, мы видим сообщение об ошибке. И, несмотря на сообщение об ошибке, скрипт продолжает выполняться. Вот почему некоторые жуки так опасны. Если дальнейшие действия сценария основаны на правильном вводе данных пользователем, поведение сценария будет непредсказуемым. Это потенциально может поставить данные под угрозу.

Причина, по которой параметр Bash -n (noexec) не находит ошибку в сценарии, заключается в том, что открывающая скобка «[» — это внешняя программа с именем [. Это не часть Баша. Это сокращенный способ использования команды test.

Bash не проверяет использование внешних программ при проверке скрипта.

Установка Шеллчек

ShellCheck требует установки. Чтобы установить его на Ubuntu, введите:

sudo apt install shellcheck

Чтобы установить ShellCheck в Fedora, используйте эту команду. Обратите внимание, что имя пакета указано в смешанном регистре, но когда вы вводите команду в окне терминала, оно все в нижнем регистре.

sudo dnf install ShellCheck

В Manjaro и подобных дистрибутивах на основе Arch мы используем pacman:

sudo pacman -S shellcheck

Использование Шеллчек

Давайте попробуем запустить ShellCheck на нашем скрипте.

shellcheck seasons.sh

ShellCheck находит проблему и сообщает нам о ней, а также предоставляет набор ссылок для получения дополнительной информации. Если щелкнуть ссылку правой кнопкой мыши и выбрать «Открыть ссылку» в появившемся контекстном меню, ссылка откроется в вашем браузере.

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

Давайте исправим нашу ошибку и заменим отсутствующий «[.» Одна из стратегий исправления ошибок состоит в том, чтобы сначала исправить проблемы с наивысшим приоритетом, а затем перейти к проблемам с более низким приоритетом, таким как предупреждения.

Мы заменили отсутствующий «[» и еще раз запустили ShellCheck.

shellcheck seasons.sh

Единственный вывод ShellCheck относится к нашему предыдущему предупреждению, так что это хорошо. У нас нет высокоприоритетных проблем, требующих решения.

Предупреждение сообщает нам, что использование команды read без параметра -r (читать как есть) приведет к тому, что любые символы обратной косой черты во входных данных будут рассматриваться как escape-символы. Это хороший пример того, какой педантичный вывод может генерировать линтер. В нашем случае пользователь все равно не должен вводить обратную косую черту — нам нужно, чтобы он вводил число.

Предупреждения, подобные этому, требуют принятия решения со стороны программиста. Приложить усилия, чтобы исправить это, или оставить все как есть? Это простое двухсекундное исправление. И это предотвратит загромождение выводом ShellCheck предупреждения, так что мы могли бы также последовать его совету. Мы добавим «r» для выбора флагов в команде read и сохраним скрипт.

read -pr "Enter a month (1 to 12): " month

Запуск ShellCheck еще раз дает нам чистую справку о здоровье.

ShellCheck — ваш друг

ShellCheck может обнаруживать, сообщать и давать рекомендации по целому ряду проблем. Посмотрите их галерею плохого кода, которая показывает, сколько типов проблем он может обнаружить.

Это бесплатно, быстро и избавляет от необходимости писать сценарии оболочки. Что не нравится?