@Kusalananda уже объяснил основную проблему и способы ее решения, а также запись Bash FAQ, на которую ссылается @glenn jackmann, также содержит много полезной информации. Вот подробное объяснение того, что происходит в моей проблеме на основе этих ресурсов.
Мы будем использовать небольшой скрипт, который печатает каждый из его аргументов в отдельной строке, чтобы проиллюстрировать вещи ( argtest.bash
):
#!/bin/bash
for var in "$@"
do
echo "$var"
done
Варианты прохождения «вручную»:
$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*
Как и ожидалось, части -rnv
и --exclude='.*'
разделяются на два аргумента, так как они разделены пробелами без кавычек (это называется разбиением по словам ).
Также обратите внимание, что кавычки .*
были удалены: одинарные кавычки сообщают оболочке о том, что они передают свое содержимое без специальной интерпретации , но сами кавычки не передаются команде .
Если теперь мы сохраняем параметры в переменной как строку (в отличие от использования массива), то кавычки не удаляются :
$ OPTS="--exclude='.*'"
$ ./argtest.bash $OPTS
--exclude='.*'
Это объясняется двумя причинами: двойные кавычки, используемые при определении, $OPTS
предотвращают специальную обработку одинарных кавычек, поэтому последние являются частью значения:
$ echo $OPTS
--exclude='.*'
Когда мы теперь используем $OPTS
в качестве аргумента команды, кавычки обрабатываются до раскрытия параметра , поэтому кавычки $OPTS
появляются «слишком поздно».
Это означает, что (в моей исходной задаче) вместо шаблона rsync
используется шаблон исключения '.*'
(с кавычками!) .*
- он исключает файлы, имя которых начинается с одинарной кавычки, за которой следует точка, и заканчивается одинарной кавычкой. Очевидно, это не то, что было задумано.
Обходным путем было бы опустить двойные кавычки при определении $OPTS
:
$ OPTS2=--exclude='.*'
$ ./argtest.bash $OPTS2
--exclude=.*
Однако рекомендуется всегда заключать в кавычки переменные из-за тонких различий в более сложных случаях.
Как отметил @Kusalananda, не цитирование .*
также сработало бы. Я добавил кавычки, чтобы предотвратить расширение шаблона , но в этом особом случае это не было строго необходимо :
$ ./argtest.bash --exclude=.*
--exclude=.*
Оказывается, что Bash делает выполнить расширение шаблона, но модель --exclude=.*
не соответствует любому файлу, поэтому шаблон передается команде. Для сравнения:
$ touch some_file
$ ./argtest.bash some_*
some_file
$ ./argtest.bash does_not_exit_*
does_not_exit_*
Однако не цитирование шаблона опасно, потому что если (по какой-либо причине) был найден соответствующий файл, --exclude=.*
шаблон расширяется:
$ touch -- --exclude=.special-filenames-happen
$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen
Наконец, давайте посмотрим, почему использование массива предотвращает мою проблему цитирования (в дополнение к другим преимуществам использования массивов для хранения аргументов команды).
При определении массива разделение слов и обработка кавычек происходит, как и ожидалось:
$ ARRAY_OPTS=( -rnv --exclude='.*' )
$ echo length of the array: "${#ARRAY_OPTS[@]}"
length of the array: 2
$ echo first element: "${ARRAY_OPTS[0]}"
first element: -rnv
$ echo second element: "${ARRAY_OPTS[1]}"
second element: --exclude=.*
При передаче параметров в команду мы используем синтаксис "${ARRAY[@]}"
, который расширяет каждый элемент массива в отдельное слово:
$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*