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

В моей системе есть два возможных источника pwd : pwd bash builtin и / usr / bin / pwd , который принадлежит GNU coreutils. Когда я выдаю cd //; pwd (используя встроенную оболочку), он набирает // . Однако, когда я выдаю cd //; / usr / bin / pwd (используя внешний), он набирает / . Фактический рабочий каталог в обоих случаях, о чем свидетельствует вывод ls и т. Д., Всегда один и тот же (корень FS).

Таким образом, ответ, похоже, состоит в том, что здесь задействованы две разные концепции: представление оболочки о своем рабочем каталоге и фактический процесс CWD. Встроенный pwd запрашивает первый, тогда как / usr / bin / pwd (и любой другой двоичный файл) будет иметь доступ только ко второму, и только первый знает что-либо о // .Я понятия не имею, почему концепция рабочего каталога в оболочке различает / и // и что должно означать // .

РЕДАКТИРОВАТЬ: Некоторые поисковые запросы наталкиваются на этот вопрос , который проливает некоторый свет. Судя по всему, странное поведение соответствует стандартам, но все же странно.

19
30.07.2018, 16:52
3 ответа

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

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

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.

58
20.08.2021, 11:14

@Kusalananda уже объяснил основную проблему и способы ее решения, а запись часто задаваемых вопросов Bash , на которую ссылается @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=.*
6
20.08.2021, 11:14

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

Например:

bash my_script.sh Hello 42 World

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

.

Ссылка на переменную $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"
0
20.08.2021, 11:14

Теги

Похожие вопросы