В скрипте Bash я пытаюсь сохранить параметры, которые я использую, rsync
в отдельной переменной. Это хорошо работает для простых опций (вроде --recursive
), но у меня возникают проблемы с --exclude='.*'
:
$ find source
source
source/.bar
source/foo
$ rsync -rnv --exclude='.*' source/ dest
sending incremental file list
foo
sent 57 bytes received 19 bytes 152.00 bytes/sec
total size is 0 speedup is 0.00 (DRY RUN)
$ RSYNC_OPTIONS="-rnv --exclude='.*'"
$ rsync $RSYNC_OPTIONS source/ dest
sending incremental file list
.bar
foo
sent 78 bytes received 22 bytes 200.00 bytes/sec
total size is 0 speedup is 0.00 (DRY RUN)
Как видите, переход --exclude='.*'
к rsync
«вручную» работает нормально ( .bar
не копируется), он не работает, когда параметры сохраняются в переменной в первую очередь.
Я предполагаю, что это связано либо с кавычками, либо с подстановочным знаком (или с обоими), но я не смог понять, что именно не так.
Ответы:
Как правило, плохая идея разбивать список отдельных элементов на одну строку, независимо от того, является ли это списком параметров командной строки или списком путей.
Используя вместо этого массив:
или
и позже...
Таким образом, сохраняется цитирование отдельных опций (до тех пор, пока вы дважды цитируете расширение
${rsync_options[@]}
). Это также позволяет вам легко манипулировать отдельными записями массива, если вам нужно сделать это перед вызовомrsync
.В любой оболочке POSIX для этого можно использовать список позиционных параметров:
Опять же, двойное цитирование расширения
$@
является критическим здесь.Тангенциально связанные:
Проблема в том, что когда вы помещаете два набора параметров в строку, одинарные кавычки значения
--exclude
параметра становятся частью этого значения. Следовательно,сработало бы ... но лучше (как в более безопасном) использовать массив или позиционные параметры с индивидуально заключенными в кавычки записями. Это также позволит вам использовать вещи с пробелами в них, если вам это понадобится, и позволит избежать того, что оболочка выполнит генерацию имени файла (глобализация) опций.
¹ при условии, что
$IFS
он не изменен и что--exclude=.
в текущем каталоге нет файла, имя которого начинается с , а параметры оболочкиnullglob
илиfailglob
не заданы.источник
@Kusalananda уже объяснил основную проблему и способы ее решения, а также запись Bash FAQ, на которую ссылается @glenn jackmann, также содержит много полезной информации. Вот подробное объяснение того, что происходит в моей проблеме на основе этих ресурсов.
Мы будем использовать небольшой скрипт, который печатает каждый из его аргументов в отдельной строке, чтобы проиллюстрировать вещи (
argtest.bash
):Варианты прохождения «вручную»:
Как и ожидалось, части
-rnv
и--exclude='.*'
разделяются на два аргумента, так как они разделены пробелами без кавычек (это называется разбиением по словам ).Также обратите внимание, что кавычки
.*
были удалены: одинарные кавычки сообщают оболочке о том, что они передают свое содержимое без специальной интерпретации , но сами кавычки не передаются команде .Если теперь мы сохраняем параметры в переменной как строку (в отличие от использования массива), то кавычки не удаляются :
Это объясняется двумя причинами: двойные кавычки, используемые при определении,
$OPTS
предотвращают специальную обработку одинарных кавычек, поэтому последние являются частью значения:Когда мы теперь используем
$OPTS
в качестве аргумента команды, кавычки обрабатываются до раскрытия параметра , поэтому кавычки$OPTS
появляются «слишком поздно».Это означает, что (в моей исходной задаче) вместо шаблона
rsync
используется шаблон исключения'.*'
(с кавычками!).*
- он исключает файлы, имя которых начинается с одинарной кавычки, за которой следует точка, и заканчивается одинарной кавычкой. Очевидно, это не то, что было задумано.Обходным путем было бы опустить двойные кавычки при определении
$OPTS
:Однако рекомендуется всегда заключать в кавычки переменные из-за тонких различий в более сложных случаях.
Как отметил @Kusalananda, не цитирование
.*
также сработало бы. Я добавил кавычки, чтобы предотвратить расширение шаблона , но в этом особом случае это не было строго необходимо :Оказывается, что Bash делает выполнить расширение шаблона, но модель
--exclude=.*
не соответствует любому файлу, поэтому шаблон передается команде. Для сравнения:Однако не цитирование шаблона опасно, потому что если (по какой-либо причине) был найден соответствующий файл,
--exclude=.*
шаблон расширяется:Наконец, давайте посмотрим, почему использование массива предотвращает мою проблему цитирования (в дополнение к другим преимуществам использования массивов для хранения аргументов команды).
При определении массива разделение слов и обработка кавычек происходит, как и ожидалось:
При передаче параметров в команду мы используем синтаксис
"${ARRAY[@]}"
, который расширяет каждый элемент массива в отдельное слово:источник
Когда мы пишем функции и сценарии оболочки, в которых аргументы передаются для обработки, аргументы будут передаваться в переменные с числовыми именами, например, $ 1, $ 2, $ 3
Например :
bash my_script.sh Hello 42 World
Внутри
my_script.sh
команды будут использоваться$1
для обращения к Hello,$2
to42
и$3
forWorld
Ссылка на переменную,
$0
расширится до имени текущего скрипта, напримерmy_script.sh
Не играйте весь код с командами в качестве переменных.
Имейте в виду :
1 Избегайте использования имен переменных с прописными буквами в скриптах.
2 Не используйте обратные кавычки, вместо этого используйте $ (...), это будет лучше.
источник