Команда в переменной ломает аргументы: собираем argv массивом Bash
Скрипт сначала собирает строку команды, а затем пытается её выполнить. Пока в пути нет пробелов, всё выглядит правильно. После добавления каталога release candidates команда получает два аргумента вместо одного. Разработчик добавляет кавычки внутрь строки, но оболочка воспринимает их как обычные символы: повторного разбора shell-синтаксиса при раскрытии переменной не происходит.
Дальше часто появляется eval. Он действительно запускает повторный разбор, но вместе с кавычками начинает понимать подстановки, перенаправления и другие конструкции, попавшие во входные данные. Для обычного выбора параметров такой разбор вообще не нужен. Команда уже состоит из известной программы и отдельных значений. Сохраним их именно в таком виде.
Массив хранит границы аргументов
Безопасный учебный вариант вместо реальной публикации вызывает функцию, которая печатает каждый полученный аргумент отдельно. Это позволяет увидеть результат без обращения к внешнему сервису.
#!/usr/bin/env bash
show_args() {
local index=0 arg
for arg in "$@"; do
printf '%d: <%s>\n' "$index" "$arg"
index=$((index + 1))
done
}
output='release candidates/report.txt'
verbose=yes
cmd=(show_args --output "$output")
if [[ $verbose == yes ]]; then
cmd+=(--verbose)
fi
cmd+=(-- 'input one.txt' 'input two.txt')
"${cmd[@]}"
Функция увидит шесть аргументов: --output, один путь с пробелом, --verbose, -- и два отдельных входных имени. Имя функции находится в нулевом элементе cmd, но в $@ вызываемой функции оно уже не входит. В выводе index=0 относится к --output. Это удобная мелочь для проверки ожидаемого результата.
Ключевая запись — "${cmd[@]}". Каждый элемент раскрывается отдельным словом. Вариант "${cmd[*]}" склеил бы весь массив в один аргумент, а некавыченное раскрытие снова открыло бы разбиение и подстановку имён файлов.
Добавляйте значения, а не куски синтаксиса
Необязательный параметр добавляется как элемент массива. Для параметра со значением добавьте сразу два элемента: имя флага и значение в кавычках. Не храните в одном элементе строку вида --output report.txt, если вызываемая программа ожидает два аргумента.
Разделитель -- в примере предназначен для той программы, которую в итоге будет запускать скрипт. Он полезен, когда входное имя может начинаться с дефиса. Но программа должна поддерживать такую форму. Массив сохраняет аргументы правильно, а смысл параметров всё равно задаёт интерфейс вызываемой утилиты.
Конвейер нельзя представить как массив argv одной команды. Символ | внутри элемента будет обычным аргументом. Если требуется конвейер, напишите его как shell-конструкцию и собирайте массивы отдельно для его участников. Это сохраняет видимость перенаправлений и позволяет осмысленно обработать статусы.
Как диагностировать реальный запуск
Перед заменой show_args на рабочую программу прогоните путь с пробелом, пустое значение, ведущий дефис и текст с символами $() как обычные данные. Ни одна из строк не должна превращаться в новую команду. Для логирования в Bash можно напечатать элементы через %q, но не запускайте полученный лог обратно: исполнять нужно исходный массив.
Массивы — расширение Bash. Если скрипт обязан работать в POSIX sh, список аргументов можно собирать через set -- и запускать команду с "$@". Не указывайте /bin/sh в shebang, сохраняя Bash-синтаксис внутри: такая ошибка проявится именно на машине, где sh не является Bash.
В программе Linux и Bash для DevOps-инженеров изучаются quoting и безопасная автоматизация. Откройте программу и продолжите к обслуживаемому скрипту, который принимает параметры, сохраняет их границы и запускает повторяемую операцию без неожиданного исполнения данных.
Ещё по теме
Продолжить на практике
- Linux и Bash для DevOps-инженеров ↗
Linux для работы с серверами: процессы, права, systemd, сеть, диски и логи. Автоматизируйте повседневные задачи на Bash и разберите типичные сбои сервиса — от занятого порта до переполненного диска.
