Практическое написание сценариев командной оболочки Bash/Команда test

Глава Ветвления →
Команда test


Команда test служит для того, чтобы эмулировать условные выражения в языке командной оболочки. Самая первая реализация test была отдельной программой, которой передавалась условная конструкция. По договоренности, эта программа возвращала 0, если переданное условие истинно, и 1 — если ложно.

Так как все сценарии изобилуют условными проверками, команда получила более короткий псевдоним в виде квадратной скобки — [. В таком виде команда описана в POSIX. Однако у оригинальной команды test специфичный перегруженный синтаксис, который часто приводит к ошибкам у начинающих программистов. В Bash поддерживается POSIX-совместимый test и вводится его усовершенствованная встроенная версия в виде оператора [[. Далее по тексту, под командой test мы будем понимать как [, так и [[, если не требуется уточнение.

Если вы пишите не портируемые сценарии, исключительно для оболочки Bash, то следует отдавать предпочтение модернизированной версии test, так как она в целом удобнее. Тем не менее, следует помнить, что в портируемых сценариях вы должны пользоваться только POSIX совместимым вариантом. Вариант [[ кроме Bash так же поддерживается в Ksh и Zsh.

В этом разделе мы рассмотрим особенности работы модернизированной команды test.

Сравнение [ и [[

[ [[
# Сравнивание строк
[ a \> b ]
[ a \< b ]
[ a = b ]
[ a != b ]
# Сравнивание строк
[[ a > b ]]
[[ a < b ]]
[[ a = b ]] или [[ a == b ]]
[[ a != b ]]
# Сравнивание чисел
[ 5 -gt 10 ]
[ 5 -lt 10 ]
[ 5 -ge 10 ]
[ 5 -le 10 ]
[ 5 -eq 10 ]
[ 5 -ne 10 ]
# Сравнивание чисел
[[ 5 -gt 10 ]] # больше
[[ 5 -lt 10 ]] # меньше
[[ 5 -ge 10 ]] # больше или равно
[[ 5 -le 10 ]] # меньше или равно
[[ 5 -eq 10 ]] # равно
[[ 5 -ne 10 ]] # не равно
# Конъюнкция и дизъюнкция
[ -n "$var" -a -f "$var" ]
[ -n "$var" -o -f "$var" ]

# Примечание:
# Важно ставить кавычки для подставляемых
# переменных, если в их значениях есть
# пробелы.
# Конъюнкция и дизъюнкция
[[ -n $var && -f $var ]]
[[ -n $var || -f $var ]]

# Примечание:
# Кавычки ставить можно, но не обязательно:
# работает одинаково.
#
# Группировка условий
[ "$var" -eq 5 -a \( "$var1" -eq 6 -o "$var1" -eq 7 \) ]
# Группировка условий
[[ $var -eq 5 && ( $var1 -eq 6 || $var1 -eq 7 ) ]]
# Регулярные выражения и маскирование
# не поддерживаются
#
# Регулярные выражения и маскирование
[[ $name = a* ]] или [[ $name == a* ]]
[[ $name =~ ^John$ ]]
Обратите внимание
  • Для старой версии test следует осторожно использовать операторы лексикографического сравнивания \> и \<, так как они являются одним из расширений стандарта POSIX. Другими словами, команда их может не поддерживать в старых системах.
  • Для старой версии test также следует осторожно использовать операторы -a, -o и группировку, так как в POSIX помечено, что это устаревшие возможности. Вместо них POSIX рекомендует использовать несколько вызовов test с объединением их операторами && и/или ||.
  • Внутри [[ не производится разбиение по разделителю IFS и Globbing, поэтому подстановку переменных не обязательно закавычивать.

Особенности использования test

править

В старых сценариях часто можно встретить такую запись

[ x"$var1" = x"$var2" ]

# или

[ "x$var1" = "x$var2" ]

Это обусловлено тем, что если переменная не определена, то она раскрывается в пустоту. С точки зрения команды test пустота не является аргументом, и если мы опустим символ x (или любой другой), то в результате команда будет выглядеть так после подстановки значений (в следующем примере мы предполагаем, что не определена var1)

var2=a_word
[ = a_word ] # если не определена var1

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

[ "$var1" = "$var2" ] # Правильно, но с очень старыми интерпретаторами может не заработать,
                      # если одна из переменных окажется не проинициализирована.

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

[[ $var1 == $var2 ]] # Правильно, если инициализация гарантирована

# Примечание:
#   Это конструкция работает всегда в Bash, по меньшей мере с версии 4.4.19(1)-release.

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

[[ ${var1:-} == ${var2:-} ]]  # Работает всегда

В Bash 4 модернизированный test умеет самостоятельно обращаться к переменным по их именам, если над ними не выполняются лексикографические операции.

NUMBER=5

# В этом примере команда test сама попытается раскрыть переменную NUMBER.
if [[ NUMBER -ge 0 ]]; then
    echo "The number is $NUMBER"
fi
# Это аналогично следующей записи.
if [[ $NUMBER -ge 0 ]]; then
    echo "The number is $NUMBER"
fi
# Если переменная не определена, то она раскроется в пустоту.
# Но для операторов сравнений чисел пустота считается нулем, поэтому следующий
# код отработает.
unset NUMBER
if [[ NUMBER -ge 0 ]]; then
    echo "The number is $NUMBER"
fi
# Вывод:
# The number is

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

Сравнивание строк и чисел

править

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

Вы можете видеть, что для сравнивания чисел у test есть свои операторы. Без этих операторов числа сравниваются как строки, т.е. используются коды символов ASCII кодировки, а не сами числа. Приведем несколько поучительных примеров.

Следующее выражение будет выполняться всегда корректно

[[ 5 == 5 ]]

Тем не менее, сравниваются не сами числа, а код символа 5 в таблице кодировки ASCII. Таким образом, оператор == будет всегда работать так же как -eq для чисел, однако, формально так делать не желательно.

Теперь рассмотрим такое выражение

[[ 99 > 888 ]]  # Возвращает ИСТИНУ

Казалось бы очевидно, что 99 меньше 888, но такая проверка будет возвращать ИСТИНУ. Связано это с тем, что лексикографически строка 99 больше 888 (код символа 9 в таблице кодировки ASCII больше 8).

Если вы хотите сравнивать числа как числа, а не строки, следует использовать один из операторов команды test: -gt, -ge, -lt, -le, -eq, -ne. Таким образом предыдущий пример следует писать так

[[ 99 -gt 888 ]]  # Возвращает ЛОЖЬ, как и ожидалось

В Ksh, Bash и Zsh также есть еще одна разновидность команды test, которая может работать только с числами. В этой версии все операторы работают с числами, как это ожидается для чисел, но в портируемых скриптах этой возможностью пользоваться нельзя.

(( 5 == 5 ))
(( 4 > 3 ))
(( 3 < 4 ))
(( 21 >= 25 ))
(( 100 <= 255 ))
(( 6 != 5 ))

Эта версия test сложнее чем кажется и поддерживает разные операторы как арифметическая подстановка, но без вывода результата (см. Bash подстановки), например

(( 2 + 2 == 4 )) # ИСТИНА



Ветвления →