В bash условие — это любая команда: if смотрит на её код возврата, 0 значит «истина», всё остальное — «ложь». [[ … ]], [ … ] и (( … )) — просто команды, которые проверяют выражение и возвращают 0 или 1.
Синтаксис
if команда; then
…
elif другая_команда; then
…
else
…
fi
for x in список; do … done
while команда; do … done
until команда; do … done
case "$var" in шаблон) … ;; esac
then, do можно писать на той же строке через ; или на следующей. Всё это пишется и в одну строку: for i in 1 2 3; do echo "$i"; done.
Три вида проверок
| Запись | Что это |
|---|
[[ выражение ]] | встроенная проверка bash: без разбиения слов, с шаблонами и регулярками — бери её по умолчанию |
[ выражение ], test выражение | POSIX-команда; работает в sh, но требует кавычек вокруг переменных |
(( выражение )) | арифметика: истина, если результат не ноль; (( n > 5 )) |
команда | любая команда: if grep -q root /etc/passwd; then … |
Проверки файлов
| Проверка | Истина, если… |
|---|
-e путь | путь существует (любой тип) |
-f путь | обычный файл |
-d путь | каталог |
-L путь / -h путь | символическая ссылка (даже битая) |
-s путь | файл существует и не пуст |
-r / -w / -x путь | есть право на чтение / запись / выполнение для текущего пользователя |
-O путь / -G путь | владелец / группа файла — текущий пользователь / его группа |
-p / -S / -b / -c путь | именованный канал / сокет / блочное / символьное устройство |
a -nt b / a -ot b | a новее / старше b по времени изменения |
a -ef b | a и b — один и тот же файл (жёсткая или символическая ссылка) |
-t 0 | дескриптор 0 — терминал (скрипт запущен интерактивно) |
Строки и числа
| Проверка | Истина, если… |
|---|
-z "$s" / -n "$s" | строка пустая / непустая |
"$a" = "$b" / "$a" != "$b" | строки равны / не равны (в [[ ]] можно ==) |
$s == шаблон | в [[ ]]: строка подходит под glob (*.log); шаблон без кавычек |
$s =~ регулярка | в [[ ]]: совпадение с ERE; группы — в ${BASH_REMATCH[1]}… |
"$a" < "$b" | строка a раньше b по алфавиту (в [ ] пиши \<) |
-eq -ne -lt -le -gt -ge | сравнение целых чисел: равно, не равно, <, <=, >, >= |
-v имя | переменная задана (даже пустая) |
Логика
| Запись | Где работает |
|---|
[[ a && b ]], [[ a || b ]], [[ ! a ]], [[ ( a || b ) && c ]] | внутри [[ ]] |
[ a ] && [ b ], [ a ] || [ b ], ! [ a ] | между любыми командами |
[ a -a b ], [ a -o b ] | внутри [ ]; устарело, лучше &&/|| снаружи |
case
| Элемент | Что значит |
|---|
шаблон) | glob: *, ?, [0-9]; a|b) — несколько вариантов |
*) | всё остальное (ставь последним) |
;; | конец ветки, дальше не проверять |
;& | выполнить и следующую ветку без проверки |
;;& | проверить и следующие шаблоны |
Циклы и управление
| Запись | Что делает |
|---|
for x in a b c | по списку слов |
for f in *.log | по файлам, подходящим под шаблон |
for i in {1..10} / {0..100..5} | по диапазону (границы — только литералы, не переменные) |
for ((i=0; i<n; i++)) | в стиле C, границы могут быть переменными |
for x | по аргументам скрипта ("$@") |
while команда / until команда | пока команда успешна / пока неуспешна |
while : / while true | бесконечный цикл, выход через break |
break / break 2 | выйти из цикла / из двух вложенных |
continue / continue 2 | перейти к следующей итерации этого / внешнего цикла |
select x in a b | меню с номерами из списка (читает stdin) |
read
| Опция | Что делает |
|---|
-r | не трактовать \ как экранирование — ставь всегда |
IFS= read -r line | сохранить ведущие и хвостовые пробелы строки |
IFS=: read -r a b c | резать строку по :; лишние поля попадают в последнюю переменную |
-a arr | слова строки — в массив |
-p 'текст' | приглашение перед вводом |
-s | не показывать ввод (пароли) |
-n N | прочитать ровно N символов, не дожидаясь Enter |
-t сек | таймаут; по истечении код возврата больше 128 |
-d символ | читать до этого символа вместо перевода строки (-d '' — до \0) |
Без имён переменных строка попадает в $REPLY. В конце файла read возвращает 1 — на этом и заканчивается while read.
Примеры
# файл существует и непуст
if [[ -s /etc/hosts ]]; then echo "есть"; fi
# число в диапазоне
n=7
if (( n >= 1 && n <= 10 )); then echo "ok"; else echo "вне диапазона"; fi
# по коду возврата команды
if grep -q '^root:' /etc/passwd; then echo "root есть"; fi
# шаблон и регулярное выражение
f="report.csv"
[[ $f == *.csv ]] && echo "csv"
if [[ $f =~ ^([a-z]+)\.([a-z]+)$ ]]; then
echo "имя ${BASH_REMATCH[1]}, расширение ${BASH_REMATCH[2]}"
fi
# короткая форма вместо if
[[ -d /tmp/work ]] || mkdir /tmp/work
# case по аргументу
case "$1" in
start|run) echo "запуск" ;;
stop) echo "остановка" ;;
*) echo "usage: $0 start|stop"; exit 1 ;;
esac
# циклы
for i in {1..5}; do echo "$i"; done
for ((i = 0; i < 3; i++)); do echo "$i"; done
for f in /etc/*.conf; do echo "$f"; done
# файл построчно, поля через двоеточие
while IFS=: read -r user _ uid _; do
(( uid >= 1000 )) && echo "$user"
done < /etc/passwd
# пропустить и прервать
for i in 1 2 3 4 5; do
(( i == 2 )) && continue
(( i == 4 )) && break
echo "$i" # 1 3
done
# ждать появления файла, но не дольше 10 попыток
for i in {1..10}; do
[[ -e /tmp/ready ]] && break
sleep 1
done
Подводные камни
- Пробелы внутри скобок обязательны.
[[-f x]] и [ "$a"="$b" ] не работают: это [[ и ]] как слова, и = должен быть отдельным аргументом.
- В
[ ] переменные — в кавычках. Если v пуста, [ $v = x ] превращается в [ = x ] — ошибка «unary operator expected». [[ ]] от этого защищён.
> и < сравнивают строки, а не числа. [[ 10 > 9 ]] — ложь («1» идёт раньше «9»). Для чисел: -gt или (( 10 > 9 )). А [ b > a ] вообще создаст файл a.
- Кавычки отключают шаблон и регулярку.
[[ $f == "*.log" ]] сравнивает с буквальной строкой *.log. Регулярку держи в переменной: re='^[0-9]+$'; [[ $s =~ $re ]].
- Шаблон без совпадений остаётся как есть.
for f in *.zzz при отсутствии файлов сделает одну итерацию с f='*.zzz'. Проверяй [[ -e $f ]] || continue или включи shopt -s nullglob.
cat f | while read … — подоболочка. Переменные, изменённые в цикле, после него пропадут. Пиши while … done < f.
- Последняя строка без
\n теряется в while read. Защита: while read -r line || [[ -n $line ]].
for line in $(cat f) идёт по словам, а не по строкам. Для строк — только while read.
{1..$n} не работает: диапазон раскрывается раньше переменных. Используй for ((i=1; i<=n; i++)) или seq.
См. также
variables · exit-codes · redirection · regex