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

Переменные и подстановки

Переменные bash, значения по умолчанию, обрезка строк, $(…), $((…)), кавычки и массивы

Переменная в bash — это строка с именем. Оболочка подставляет её значение везде, где встречает $имя, а затем (если нет кавычек) режет результат на слова и раскрывает шаблоны вроде *. Поэтому кавычки здесь важнее, чем кажется.

Синтаксис

имя=значение            # без пробелов вокруг =
echo "$имя"             # подстановка значения
echo "${имя}_suffix"    # фигурные скобки отделяют имя от текста
export имя              # сделать переменную видимой дочерним процессам
unset имя               # удалить переменную

Имя — латинские буквы, цифры и _, не может начинаться с цифры. Регистр важен: Name и NAME — разные переменные.

Кавычки

ЗаписьЧто происходит
"…"подстановки $var, $(…), $((…)) работают, но результат не режется на слова и не раскрывается как шаблон
'…'всё буквально, никаких подстановок; одинарную кавычку внутри не поставить
$'…'буквально, но понимает escape-последовательности: $'a\tb', $'\n'
\символэкранировать один символ: \$, \", \\, \
без кавычекзначение режется по $IFS (пробел, табуляция, перевод строки) и раскрывается как glob

Объявление: declare и компания

КомандаЧто делает
export var=x / declare -x varпеременная попадёт в окружение дочерних процессов; export -n var — убрать оттуда
local var=xпеременная видна только внутри функции
readonly var=x / declare -rконстанта: переприсвоить нельзя (в скрипте это ошибка и выход)
declare -i nцелое число: n=5+3 сохранит 8
declare -l / declare -uпри присваивании переводить в нижний / верхний регистр
declare -a arr / declare -A mapобычный / ассоциативный массив
declare -p varпоказать, как объявлена переменная, с типом и значением
VAR=x командапеременная только для этой одной команды

Подстановки параметров

ФормаЧто даёт
${var:-x}значение var, а если она пуста или не задана — x
${var-x}x, только если var не задана вовсе (пустая строка считается значением)
${var:=x}как :-, но ещё и присваивает x переменной
${var:+x}x, если var непуста, иначе пустая строка
${var:?текст}ошибка с текстом и выход из скрипта, если var пуста
${#var}длина строки в символах
${var#шаблон} / ${var##шаблон}срезать с начала самое короткое / самое длинное совпадение
${var%шаблон} / ${var%%шаблон}срезать с конца самое короткое / самое длинное совпадение
${var/a/b} / ${var//a/b}заменить первое / все вхождения a на b; ${var//a/} — удалить
${var/#a/b} / ${var/%a/b}заменить a, только если строка с него начинается / им заканчивается
${var:pos} / ${var:pos:len}подстрока с позиции pos (с нуля); ${var: -3} — последние 3 символа (пробел перед - обязателен)
${var^} / ${var^^}первую букву / всю строку в верхний регистр
${var,} / ${var,,}первую букву / всю строку в нижний регистр
${!var}косвенная ссылка: значение переменной, имя которой лежит в var
${var@Q}значение в кавычках, пригодное для повторного ввода в оболочку

Шаблоны в #, %, / — это glob (*, ?, […]), а не регулярные выражения.

Подстановка команд и арифметика

ФормаЧто даёт
$(команда)stdout команды; завершающие переводы строк срезаются. Старая форма — `команда`
$((выражение))результат целочисленного выражения
((выражение))вычислить без подстановки; код возврата 0, если результат не ноль
let "z=2*3"то же, что ((z=2*3))

Операторы в $((…)): + - * / % **, ++ --, += -= *= /=, сравнения < <= > >= == !=, логика && || !, побитовые & | ^ ~ << >>, тернарный a ? b : c. Внутри $((…)) знак $ перед именами можно не писать. Числа: 0x1F — шестнадцатеричное, 010 — восьмеричное, 2#101 — двоичное, 10#08 — явно десятичное.

Массивы

ЗаписьЧто даёт
arr=(a b "c d")создать массив из трёх элементов
arr[5]=xприсвоить по индексу (дырки допустимы)
arr+=(e f)добавить элементы в конец
${arr[0]} / ${arr[-1]}первый / последний элемент
"${arr[@]}"все элементы, каждый отдельным словом
"${arr[*]}"все элементы одной строкой через первый символ $IFS
${#arr[@]}число элементов
${!arr[@]}список индексов (или ключей ассоциативного массива)
"${arr[@]:1:2}"срез: 2 элемента начиная с индекса 1
unset 'arr[1]'удалить элемент (индексы не сдвигаются)
declare -A m; m[key]=vассоциативный массив: ключи — строки
mapfile -t arr < файлпрочитать файл в массив, по строке на элемент
IFS=, read -ra arr <<< "a,b,c"разрезать строку по разделителю в массив

Специальные переменные

ПеременнаяЗначение
$0имя скрипта
$1 … $9, ${10}позиционные аргументы; shift сдвигает их влево
$#число аргументов
"$@"все аргументы, каждый отдельным словом — почти всегда нужен именно он
"$*"все аргументы одной строкой
$?код возврата последней команды
$$PID текущей оболочки
$!PID последнего фонового процесса
$_последний аргумент предыдущей команды
$PIPESTATUSмассив кодов возврата частей последнего конвейера
$BASH_REMATCHгруппы последнего совпадения [[ … =~ … ]]
$IFSразделители слов (по умолчанию пробел, табуляция, перевод строки)
$PWD, $OLDPWD, $HOME, $UIDтекущий и предыдущий каталог, домашний каталог, числовой UID
$RANDOM, $SECONDS, $LINENOслучайное число 0–32767, секунды с запуска оболочки, номер строки

Примеры

name=world
echo "hello $name"            # hello world
echo 'hello $name'            # hello $name — в одинарных кавычках подстановок нет

# значение по умолчанию
echo "${PORT:-8080}"

# обязательный параметр скрипта: без него — ошибка и выход
: "${1:?укажи имя файла}"

# разобрать путь без basename/dirname
p=/var/log/nginx/access.log.gz
echo "${p##*/}"               # access.log.gz — имя файла
echo "${p%/*}"                # /var/log/nginx — каталог
echo "${p%.gz}"               # /var/log/nginx/access.log — без расширения
echo "${p##*.}"               # gz — только расширение
echo "${p%%.*}"               # /var/log/nginx/access — до первой точки

# замена и регистр
s="a-b-c"
echo "${s//-/_}"              # a_b_c
echo "${s^^}"                 # A-B-C

# вывод команды в переменную
today=$(date +%F)

# арифметика
echo $((7 / 2)) $((7 % 2)) $((2 ** 10))   # 3 1 1024
i=5; ((i++)); ((i += 10)); echo "$i"      # 16

# массивы
arr=(one two "three four")
echo "${#arr[@]}"             # 3
arr+=(five)
for x in "${arr[@]}"; do echo "<$x>"; done

# ассоциативный массив
declare -A port=([http]=80 [https]=443)
for k in "${!port[@]}"; do echo "$k=${port[$k]}"; done

# форматировать число в переменную без подоболочки
printf -v id '%05d' 42        # id=00042

# переменная только для одной команды
LC_ALL=C sort file.txt

Подводные камни

  • Пробелы вокруг = ломают присваивание. a = 1 — это запуск команды a с аргументами = и 1 (command not found).
  • Переменная без кавычек режется на слова. Если f="my file.txt", то ls $f ищет два файла my и file.txt. Пиши "$f" почти всегда.
  • "${arr[@]}" и ${arr[@]} — разные вещи. Без кавычек элемент "three four" развалится на два. То же с "$@" против $*.
  • Арифметика только целочисленная. $((0.5 + 1)) — синтаксическая ошибка, $((7/2)) — это 3. Дроби считай в awk.
  • Ведущий ноль — восьмеричное число. $((010)) равно 8, а $((08)) — ошибка «value too great for base». Для чисел вроде 08 из даты пиши $((10#08)).
  • Без export дочерний процесс переменную не видит. x=1; bash -c 'echo $x' печатает пустую строку.
  • $(…) срезает завершающие переводы строк, но не ведущие пробелы. А local x=$(команда) теряет код возврата команды: $? будет кодом local.
  • ${var: -1} и ${var:-1} — разные вещи. Без пробела это «значение по умолчанию 1», а не «последний символ».
  • Изменения переменных внутри конвейера теряются: каждая часть | работает в подоболочке.

См. также

conditions-loops · redirection · exit-codes · shellcheck