Сравнение условных конструкций Bash: когда использовать test, [ и [[
Сравните test, одинарные и двойные скобки, чтобы ваши условные конструкции Bash оставались переносимыми, безопасными и читаемыми.
Сравнение условных конструкций Bash: когда использовать test, [ и [[
Когда условная конструкция Bash ведет себя странно, проблема часто кроется в выбранной вами конструкции. test, [ ] и [[ ]] выглядят похоже, но по-разному обрабатывают кавычки, шаблоны, регулярные выражения и переносимость.
Это руководство сравнивает три формы, чтобы вы могли писать условные конструкции, которые безопасны, читаемы и подходят для оболочки, в которой фактически выполняется ваш скрипт.
Команда test: Основа
Команда test — один из старейших и наиболее фундаментальных способов оценки условий в shell-скриптах. Это встроенная команда в большинстве современных оболочек и часть стандарта POSIX, что делает ее высокопереносимой. test вычисляет выражение и возвращает код завершения 0 (истина) или 1 (ложь).
Базовое использование
Команда test принимает один или несколько аргументов, которые образуют вычисляемое выражение. Она проверяет атрибуты файлов, сравнивает строки и целые числа.
# Проверка существования файла
if test -f "myfile.txt"; then
echo "myfile.txt существует и является обычным файлом."
fi
# Проверка равенства двух строк
NAME="Alice"
if test "$NAME" = "Alice"; then
echo "Имя — Alice."
fi
# Проверка, больше ли одно число другого
COUNT=10
if test "$COUNT" -gt 5; then
echo "Count больше 5."
fi
Распространенные операторы test
- Файловые операторы:
-f(обычный файл),-d(каталог),-e(существует),-s(не пустой),-r(читаемый),-w(записываемый),-x(исполняемый). - Строковые операторы:
=(равно),!=(не равно),-z(строка пуста),-n(строка не пуста). - Операторы для целых чисел:
-eq(равно),-ne(не равно),-gt(больше),-ge(больше или равно),-lt(меньше),-le(меньше или равно).
Совет: Всегда заключайте переменные, используемые с test, в кавычки (например, "$NAME"), чтобы избежать проблем с разбиением слов и раскрытием шаблонов имен файлов, если значение переменной содержит пробелы или символы подстановки.
Одинарные скобки [ ]: Форма test
Конструкция с одинарными скобками [ ] является альтернативным синтаксисом для команды test. Во многих оболочках [ — это встроенная команда оболочки, а в системах также часто присутствует внешняя /usr/bin/[. Ключевое отличие в том, что [ требует закрывающую ] в качестве последнего аргумента. Как и test, она совместима с POSIX.
Синтаксис и семантика
# Эквивалентно test -f "myfile.txt"
if [ -f "myfile.txt" ]; then
echo "myfile.txt существует и является обычным файлом с использованием [ ]."
fi
# Эквивалентно test "$NAME" = "Alice"
NAME="Bob"
if [ "$NAME" != "Alice" ]; then
echo "Имя не Alice."
fi
Обратите внимание на обязательный пробел после [ и перед ]. Они рассматриваются как отдельные аргументы команды [.
Заключение переменных в кавычки: Критическая деталь
Поскольку [ ] по сути является командой test, она наследует те же особенности поведения в отношении разбиения слов и раскрытия шаблонов имен файлов. Это означает, что незаключенные в кавычки переменные могут привести к неожиданному поведению или уязвимостям безопасности.
Рассмотрим этот пример:
#!/bin/bash
INPUT="file with spaces.txt"
# ОПАСНО: Незаключенная в кавычки переменная вызовет проблемы, если INPUT содержит пробелы
# Оболочка выполнит разбиение слов, обрабатывая "file" и "with spaces.txt" как отдельные аргументы,
# что приведет к синтаксической ошибке или неверной оценке.
# if [ -f $INPUT ]; then echo "Found"; else echo "Not found"; fi
# ПРАВИЛЬНО: Заключите переменную в кавычки, чтобы обрабатывать ее как единый аргумент
if [ -f "$INPUT" ]; then
echo "'file with spaces.txt' существует."
else
echo "'file with spaces.txt' не существует или не является обычным файлом."
fi
Без кавычек $INPUT развернется в file with spaces.txt, и [ -f file with spaces.txt ] будет интерпретироваться командой [ как синтаксическая ошибка, поскольку -f ожидает только один операнд. Заключение в кавычки гарантирует, что $INPUT будет передан как единый аргумент "file with spaces.txt".
Опасности разбиения слов и раскрытия шаблонов имен файлов
Как test, так и [ подвержены стандартному поведению оболочки по разбиению слов и раскрытию шаблонов имен файлов (globbing). Если переменная содержит пробелы или символы подстановки (*, ?, [ ]) и не заключена в кавычки, оболочка развернет ее до того, как test или [ увидят аргументы. Это может привести к синтаксическим ошибкам или неверным сравнениям, когда символы подстановки совпадают с существующими файлами.
Двойные скобки [[ ]]: Современное ключевое слово Bash
Конструкция с двойными скобками [[ ]] является ключевым словом Bash (также поддерживается в Ksh и Zsh), а не внешней командой или псевдонимом. Это различие имеет решающее значение, поскольку позволяет [[ ]] вести себя иначе и предлагает расширенную функциональность и повышенную безопасность по сравнению с test или [ ].
Расширенная функциональность
[[ ]] вводит несколько мощных функций, недоступных в test или [:
Отсутствие разбиения слов и раскрытия шаблонов имен файлов: Переменные внутри
[[ ]]обычно не нужно заключать в кавычки (хотя для ясности это часто хорошая практика). Оболочка обрабатывает содержимое[[ ]]как единое целое, предотвращая разбиение слов и раскрытие шаблонов имен файлов. Это значительно снижает распространенные ошибки сценариев и риски безопасности.# Нет необходимости заключать переменные в кавычки (хотя это все равно безопасно) INPUT="file with spaces.txt" if [[ -f $INPUT ]]; then # $INPUT здесь обрабатывается как единая строка echo "'$INPUT' существует." fiПодстановка для сравнения строк: Операторы
==и!=выполняют сопоставление с образцом (globbing), а не строгое равенство строк при использовании внутри[[ ]]. Это означает, что вы можете использовать*,?и[]в качестве подстановочных знаков.FILE_NAME="my_document.txt" if [[ "$FILE_NAME" == *".txt" ]]; then # Проверяет, заканчивается ли FILE_NAME на .txt echo "Это текстовый файл!" fi # Примечание: Для строгого равенства строк без подстановки используйте `test` или `[ ]` с `=` # или убедитесь, что в правой части `==` в [[ ]] нет символов подстановки # (или заключите правую часть в кавычки, если она содержит буквальные символы подстановки, которые нужно сопоставить буквально).Сопоставление с регулярными выражениями: Оператор
=~позволяет выполнять сопоставление с регулярными выражениями.
IP_ADDRESS="192.168.1.100"
if [[ "$IP_ADDRESS" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
echo "Допустимый формат IP."
fi
# Важно: Шаблон регулярного выражения в правой части =~ обычно НЕ должен заключаться в кавычки,
# если он содержит символы, которые в противном случае были бы обработаны как шаблоны подстановки.
# Если регулярное выражение находится в переменной, его также следует оставить без кавычек.
# Пример шаблона: ^[A-Za-z]+$
```
4. **Логические операторы `&&` и `||`**: `[[ ]]` поддерживает более интуитивные логические операторы в стиле C `&&` (И) и `||` (ИЛИ) для объединения нескольких условий, а также `!` для отрицания. Эти операторы имеют правильное сокращенное вычисление и приоритет, в отличие от `-a` и `-o` в `test`.
```bash
AGE=25
if [[ "$NAME" == "Alice" && "$AGE" -ge 18 ]]; then
echo "Алиса совершеннолетняя."
fi
if [[ "$USER" == "root" || -w /etc/fstab ]]; then
echo "Либо root, либо есть права на запись в fstab."
fi
```
### Специфичность для Bash
Хотя `[[ ]]` предлагает значительные преимущества, его главный недостаток в том, что это расширение Bash/Ksh/Zsh и не является частью стандарта POSIX. Это означает, что скрипты, полагающиеся на `[[ ]]`, могут быть не переносимы на `sh`, `dash` или старые/минималистичные Unix-подобные системы.
## Сравнение бок о бок: `test` vs. `[` vs. `[[`
Вот таблица, суммирующая ключевые различия:
| Особенность | `test` | `[ ]` | `[[ ]]` |
| :------------------------- | :------------------------------- | :---------------------------------- | :---------------------------------------- |
| **Тип** | Встроенная команда (или внешняя) | Встроенная команда (псевдоним `test`) | Ключевое слово оболочки (Bash, Ksh, Zsh) |
| **Совместимость с POSIX** | Да | Да | Нет |
| **Требуется закрывающая `]`** | Нет | Да (как последний аргумент) | Да (как часть ключевого слова) |
| **Разбиение слов** | Да, для незаключенных в кавычки переменных | Да, для незаключенных в кавычки переменных | Нет, переменные обрабатываются как единые строки |
| **Раскрытие шаблонов имен файлов** | Да, для незаключенных в кавычки переменных | Да, для незаключенных в кавычки переменных | Нет |
| **Сопоставление с образцом (globbing)** | Нет для равенства строк | Нет для равенства строк | Да с незаключенной в кавычки правой частью `==` или `!=` |
| **Регулярные выражения** | Нет | Нет | Да с `=~` |
| **Логические И/ИЛИ** | `-a`, `-o` существуют, но их легко неправильно прочитать | `-a`, `-o` существуют, но их легко неправильно прочитать | `&&`, `||` с обычным сокращенным вычислением |
| **Составные команды** | Требует отдельных вызовов `test` | Требует отдельных вызовов `[` | Может комбинировать выражения напрямую (`&&`/`||`)|
| **Заключение переменных в кавычки** | **Обязательно** для безопасности | **Обязательно** для безопасности | Обычно не требуется, но хорошая практика |
## Когда что использовать
Выбор правильной условной конструкции зависит в первую очередь от ваших требований к переносимости и сложности вашей условной логики.
### Совместимость с POSIX против современных функций Bash
- **Используйте `test` или `[ ]`, когда...**
- **Переносимость имеет первостепенное значение**: Если ваш скрипт должен работать в любой POSIX-совместимой оболочке (`sh`, `dash`, старые системы и т.д.), `test` или `[ ]` — ваши единственные надежные варианты.
- Ваши условия просты (проверки файлов, базовые сравнения строк/целых чисел).
- Вы готовы тщательно заключать все переменные в кавычки и использовать `&&`/`||` на уровне оболочки вне скобок, когда вам нужна составная логика.
- **Используйте `[[ ]]`, когда...**
- **Вы пишете исключительно для Bash** (или Ksh/Zsh) и вам не нужна переносимость POSIX.
- Вам требуются расширенные функции, такие как сопоставление с образцом (globbing), сопоставление с регулярными выражениями или логические операторы в стиле C `&&`/`||`.
- Вы хотите воспользоваться расширенными функциями безопасности, которые предотвращают разбиение слов и раскрытие шаблонов имен файлов, что приводит к более надежному и менее подверженному ошибкам коду.
- Ваши условия включают сложную логику, которая была бы громоздкой с `test -a`/`-o`.
### Лучшие практики и рекомендации
1. **Отдавайте приоритет `[[ ]]` для скриптов Bash**: Если ваш скрипт предназначен для Bash, `[[ ]]` обычно является предпочтительным выбором из-за повышенной безопасности, расширенной функциональности и более интуитивного синтаксиса для сложных условий. Это значительно сокращает распространенные ошибки сценариев, связанные с кавычками и специальными символами.
2. **Всегда заключайте в кавычки в `test` и `[ ]`**: Если вы *должны* использовать `test` или `[ ]` для совместимости с POSIX, возьмите за привычку **всегда заключать переменные в кавычки**, чтобы предотвратить неожиданное поведение из-за разбиения слов и раскрытия шаблонов имен файлов.
```bash
# Хорошая практика для [ ] и test
VAR="строка с пробелами"
if [ -n "$VAR" ]; then echo "Не пусто"; fi
```
3. **Помните о сопоставлении с образцом**: В `test` и `[ ]` `=` используется для равенства строк. В `[[ ]]` `=` и `==` могут выполнять сопоставление с образцом, когда правая часть не заключена в кавычки. Заключайте правую часть в кавычки, когда вам нужно буквальное сравнение строк.
4. **Регулярные выражения с `=~`**: При использовании `=~` в `[[ ]]` правая часть обычно должна быть без кавычек, чтобы позволить оболочке интерпретировать ее как шаблон регулярного выражения, а не как буквальную строку для сопоставления.
```bash
# Шаблон регулярного выражения без кавычек правильный для =~ в [[ ]]
if [[ "$LINE" =~ ^Error: ]]; then echo "Ошибка найдена"; fi
```
## Вывод
Используйте `[ ]` или `test`, когда ваш скрипт должен работать под POSIX `sh`. Используйте `[[ ]]`, когда ваш shebang — Bash, и вы хотите более безопасную обработку переменных, сопоставление с шаблонами, сопоставление с регулярными выражениями и более чистые составные условия. Главная привычка проста: подбирайте синтаксис условных конструкций под оболочку и осознанно используйте кавычки.