Переменные и кавычки
Переменные в bash хранят строки; кавычки определяют, раскрывает ли оболочка, разбивает по словам или передаёт значения буквально. Переменные без кавычек разбиваются по пробелам и раскрываются как glob-шаблоны — источник большинства ошибок в начинающих скриптах.
Скрипт, который отлично работает с каталогом logs, молча уничтожает данные в каталоге my logs — два слова, один пробел, одна катастрофа. Виновник почти всегда — переменная без кавычек. Bash обрабатывает каждый $VAR без кавычек через два дополнительных шага: разбиение по словам (разбить значение по IFS, обычно пробелу) и glob-раскрытие (заменить *, ?, [...] именами файлов). Двойные кавычки подавляют оба шага. Одиночные кавычки подавляют всё, включая раскрытие переменных. Понимание того, какую форму кавычек использовать и когда, — это главный шаг от «пишет скрипты» к «пишет правильные скрипты».
После этого урока ты сможешь правильно присваивать и читать переменные оболочки, использовать "$VAR" для предотвращения разбиения по словам и glob-раскрытия, использовать ${VAR} для однозначного раскрытия, использовать $() для подстановки команд и осознанно выбирать между одиночными и двойными кавычками.
Присваивай переменную без пробелов вокруг =. Любой пробел заставляет bash интерпретировать команду иначе.
name="Alice" # правильно
name = "Alice" # НЕВЕРНО — bash видит команду 'name' с аргументами '=' и '"Alice"'Читай значение через $name или ${name}. Фигурные скобки обязательны, когда имя переменной идёт непосредственно перед другим текстом:
prefix="back"
echo "${prefix}up" # выводит: backup
echo "$prefixup" # ничего не выводит — оболочка ищет переменную с именем 'prefixup'В скриптах всегда предпочитай ${VAR}. Скобки ничего не стоят и предотвращают молчаливое неправильное раскрытие.
Неэкранированный $VAR проходит через разбиение по словам, а затем через glob-раскрытие. Разбиение по словам делит значение по каждому символу в $IFS (по умолчанию: пробел, табуляция, перевод строки). Glob-раскрытие затем заменяет токены *, ? или [...] именами совпадающих файлов.
files="report 2024.txt"
rm $files # bash разбивает по пробелу → запускает: rm report 2024.txt (два аргумента!)
rm "$files" # правильно — один аргумент: "report 2024.txt"Форма без кавычек пытается удалить два файла: report и 2024.txt. Ни один не существует — получаешь ошибки. Если файл report существовал бы, он был бы удалён. Форма с кавычками правильно адресует один файл.
Двойные кавычки "..." подавляют разбиение по словам и glob, но всё ещё раскрывают переменные. Это правильный вариант по умолчанию для переменных в скриптах.
dir="/home/alice/my projects"
ls "$dir" # правильно — один аргумент для ls
ls $dir # НЕВЕРНО — ls получает два аргумента: /home/alice/my и projectsВнутри двойных кавычек: $VAR, ${VAR}, $(cmd) и экранирование \ всё ещё работают. Всё остальное — буквально.
echo "Сегодня $(date +%F)" # раскрывает подоболочку date, выводит: Сегодня 2024-11-15
echo "Цена: \$100" # \$ экранирует знак доллара — выводит: Цена: $100Одиночные кавычки '...' подавляют всё — никакого раскрытия переменных, никаких подстановок. Значение берётся полностью буквально.
echo '$HOME' # выводит буквальную строку: $HOME
echo "$HOME" # выводит: /home/aliceОдиночные кавычки полезны, когда нужна строка со специальными символами и ты хочешь нулевой обработки:
grep -E '^[0-9]+\.[0-9]+' file.txt # regex не нуждается в раскрытии — одиночные кавычки безопасны
pattern='^[0-9]+\.[0-9]+'
grep -E "$pattern" file.txt # хранится в переменной — двойные кавычки правильныНельзя вставить одиночную кавычку внутрь одиночных кавычек. Чтобы включить её, закрой строку, напиши \' и снова открой: 'it'\''s' даёт it's.
$(команда) запускает команду в подоболочке и подставляет её stdout. Это заменяет старый синтаксис с обратными кавычками `команда`, который сложнее вкладывать и читать.
today=$(date +%F)
echo "Дата резервной копии: $today"
# Вложение чистое с $()
count=$(wc -l < $(ls -t *.log | head -1))
echo "В последнем логе $count строк"Всегда заключай результаты подстановки команд в кавычки при присваивании переменной или передаче в качестве аргументов: "$(cmd)". Вывод $(cmd) проходит через разбиение по словам и glob точно так же, как любое значение без кавычек.
Безопасное резервное копирование файла с именем по дате.
#!/usr/bin/env bash
src="$1"
dest_dir="$2"
stamp=$(date +%Y%m%d_%H%M%S)
dest="${dest_dir}/${stamp}_$(basename "$src")"
cp "$src" "$dest"
echo "Скопировано: $src → $dest"Каждая переменная заключена в кавычки. $(date ...) и $(basename "$src") используют подстановку команд. "$src" внутри basename тоже в кавычках — имена файлов с пробелами выживают во всём конвейере. Если бы src где-либо был без кавычек, имя вроде my report.txt молча разбилось бы на два аргумента.
▸Почему это работает
На macOS bash версии 3.2 (выпущен в 2006 году, последняя версия под GPLv2, которую поставляет Apple). Все правила кавычек в этом уроке работают идентично на bash 3.2. Различия проявляются только в расширенных возможностях: ассоциативные массивы (declare -A, bash 4+) и mapfile/readarray. Для переносимости установи bash 5 через Homebrew (brew install bash) и используй #!/usr/bin/env bash, чтобы скрипты брали первый bash из $PATH.
▸Частая ошибка
Частая ошибка — заключать само присваивание в кавычки: name="$(whoami)" правильно, но "name=$(whoami)" делает всю строку буквальным именем команды — bash пытается запустить команду с именем name=alice. Присваивание никогда не нуждается во внешних кавычках; только правая часть значения нуждается в кавычках, если содержит пробелы или специальные символы. Ещё ловушка: export "VAR=value" выглядит безвредно, но непереносимо; пиши export VAR="value".
Скрипт содержит: f='my file.txt'; rm $f. Что bash фактически выполняет?
Переменные оболочки хранят обычные строки. Чтение без кавычек запускает разбиение по словам (по $IFS) и glob-раскрытие — корень большинства ошибок с кавычками. Двойные кавычки ("$VAR") раскрывают переменную, но подавляют разбиение и glob; используй их по умолчанию для любого значения, которое может содержать пробелы. Одиночные кавычки ('буквально') подавляют всё раскрытие — полезны для regex-шаблонов и фиксированных строк. Фигурные скобки ${VAR} предотвращают неоднозначность, когда имя стоит рядом с другим текстом. $(cmd) запускает команду и подставляет её stdout; заключай результат в кавычки при дальнейшей передаче. Правило простое: при сомнении — двойные кавычки.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.