← Весь справочник

Условия и циклы

if, [[ ]], test, case, циклы for/while/until, чтение файла построчно, break и continue

В 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 ba новее / старше b по времени изменения
a -ef ba и 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