Использование переменных оболочки для параметров команды

19

В скрипте 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не копируется), он не работает, когда параметры сохраняются в переменной в первую очередь.

Я предполагаю, что это связано либо с кавычками, либо с подстановочным знаком (или с обоими), но я не смог понять, что именно не так.

Флориан Брукер
источник
1
Или посмотрите этот ответ на нашем собственном сайте.
Скотт

Ответы:

38

Как правило, плохая идея разбивать список отдельных элементов на одну строку, независимо от того, является ли это списком параметров командной строки или списком путей.

Используя вместо этого массив:

rsync_options=( -rnv --exclude='.*' )

или

rsync_options=( -r -n -v --exclude='.*' )

и позже...

rsync "${rsync_options[@]}" source/ target

Таким образом, сохраняется цитирование отдельных опций (до тех пор, пока вы дважды цитируете расширение ${rsync_options[@]}). Это также позволяет вам легко манипулировать отдельными записями массива, если вам нужно сделать это перед вызовом rsync.

В любой оболочке POSIX для этого можно использовать список позиционных параметров:

set -- -rnv --exclude='.*'

rsync "$@" source/ target

Опять же, двойное цитирование расширения $@является критическим здесь.

Тангенциально связанные:


Проблема в том, что когда вы помещаете два набора параметров в строку, одинарные кавычки значения --excludeпараметра становятся частью этого значения. Следовательно,

RSYNC_OPTIONS='-rnv --exclude=.*'

сработало бы ... но лучше (как в более безопасном) использовать массив или позиционные параметры с индивидуально заключенными в кавычки записями. Это также позволит вам использовать вещи с пробелами в них, если вам это понадобится, и позволит избежать того, что оболочка выполнит генерацию имени файла (глобализация) опций.


¹ при условии, что $IFSон не изменен и что --exclude=.в текущем каталоге нет файла, имя которого начинается с , а параметры оболочки nullglobили failglobне заданы.

Кусалананда
источник
Использование массива отлично работает, спасибо за подробный ответ!
Флориан Брукер
3

@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=.*
Флориан Брукер
источник
Это долго меня смущало, поэтому подробное объяснение, как это, полезно.
Джо
0

Когда мы пишем функции и сценарии оболочки, в которых аргументы передаются для обработки, аргументы будут передаваться в переменные с числовыми именами, например, $ 1, $ 2, $ 3

Например :

bash my_script.sh Hello 42 World

Внутри my_script.shкоманды будут использоваться $1для обращения к Hello, $2to 42и $3forWorld

Ссылка на переменную, $0расширится до имени текущего скрипта, напримерmy_script.sh

Не играйте весь код с командами в качестве переменных.

Имейте в виду :

1 Избегайте использования имен переменных с прописными буквами в скриптах.

2 Не используйте обратные кавычки, вместо этого используйте $ (...), это будет лучше.

if [ $# -ne 2 ]
then
    echo "Usage: $(basename $0) DIRECTORY BACKUP_DIRECTORY"
    exit 1
fi

directory=$1
backup_directory=$2
current_date=$(date +%Y-%m-%dT%H-%M-%S)
backup_file="${backup_directory}/${current_date}.backup"

tar cv "$directory" | openssl des3 -salt | split -b 1024m - "$backup_file"
Чемпион-бегун
источник