Кавычки, подстановки и glob: как shell разбирает команду
Автор: Казачкин Даниил Михайлович · Обновлено
Shell сначала разбирает командную строку, выполняет подстановки и только затем запускает программу с готовым массивом аргументов. Большинство загадочных ошибок с пробелами, звёздочками и переменными возникает до старта самой утилиты.
Что меняют кавычки
Одинарные кавычки сохраняют все символы буквально. Двойные разрешают подстановку переменных и команд, но удерживают результат одним аргументом. Без кавычек shell может разделить значение по пробелам и дополнительно раскрыть glob-шаблоны.
project_name='demo project'
printf '<%s>\n' "$project_name"
printf '<%s>\n' '$project_name'
printf '<%s>\n' $project_nameПервая команда получает один аргумент demo project, вторая — буквальный текст $project_name, третья обычно получает два аргумента. Такое случайное дробление особенно опасно для путей и списков, полученных из внешних данных.
Glob не является регулярным выражением
Шаблон *.log раскрывает сам shell в список существующих имён текущего каталога. Точка означает обычную точку, а * — любую последовательность символов. Скрытые имена, начинающиеся с точки, по умолчанию не совпадают с обычной звёздочкой.
mkdir -p /tmp/glob-demo
touch '/tmp/glob-demo/app one.log' /tmp/glob-demo/app-two.log
printf '%s\n' /tmp/glob-demo/*.logКаждый найденный путь передаётся отдельным аргументом; пробел внутри имени не делит его повторно после pathname expansion. Но если совпадений нет, Bash без nullglob может оставить шаблон как буквальную строку, и программа попробует открыть файл со звёздочкой в имени.
Подстановка команды
$(command) заменяется stdout вложенной команды без завершающих переводов строки. Результат почти всегда следует заключать в двойные кавычки.
current_branch=$(git branch --show-current)
printf 'branch=%s\n' "$current_branch"Не используйте результат подстановки как готовую команду. Строка не является безопасным массивом аргументов; для сложного вызова сформируйте Bash-массив и передайте его через "${args[@]}".
Ошибка и диагностика
Симптом «команда работает вручную, но ломается на имени с пробелом» почти всегда требует посмотреть реальные аргументы. Добавьте printf '<%q>\n' ... или временно включите set -x; Bash покажет команды после раскрытия. Не исправляйте проблему добавлением eval: он повторно интерпретирует строку и способен превратить данные в код.
Практика
Создайте файлы report one.txt, report-two.txt и .hidden.txt. Сравните вывод quoted и unquoted переменной с путём, glob *.txt и буквальный шаблон '*.txt'. Затем соберите массив из двух путей и передайте его в printf '<%s>\n' "${files[@]}". Объясните число аргументов в каждом случае.
Что важно запомнить
- Программа получает уже разобранные аргументы; кавычки управляют работой shell до запуска.
- Двойные кавычки сохраняют значение одним аргументом, одинарные отключают подстановки.
- Glob сопоставляет имена файлов и не заменяет регулярные выражения.
Частые вопросы
Нужно ли заключать "$variable" в кавычки всегда?
Для строковых данных это безопасное правило по умолчанию. Есть контексты Bash, где дробления нет, но явные кавычки делают намерение понятнее.
Почему *.log иногда печатается буквально?
Если совпадений нет, стандартное поведение Bash сохраняет шаблон. Опция nullglob удаляет его, а failglob делает отсутствие совпадений ошибкой.
Почему eval считается опасным?
eval запускает второй цикл разбора shell. Символы, пришедшие как данные, могут стать операторами или командами. Массивы почти всегда решают задачу без повторной интерпретации.