Это обсуждалось в ряде вопросов о unix.SE, я постараюсь собрать все вопросы, которые могут возникнуть здесь. Ссылки в конце.
Почему это не удается
Причина, по которой вы сталкиваетесь с этими проблемами, заключается в разделении слов, и тот факт, что кавычки, расширенные из переменных, не действуют как кавычки, а являются просто обычными символами.
Случаи, представленные в вопросе:
$ abc='ls -l "/tmp/test/my dir"'
Здесь $abc
разделяется и ls
получает два аргумента "/tmp/test/my
и dir"
(с кавычками в начале первого и в конце второго):
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
Здесь расширение цитируется, поэтому оно хранится как одно слово. Оболочка пытается найти программу с именем ls -l "/tmp/test/my dir"
, включая пробелы и кавычки.
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
И здесь $abc
в качестве аргумента берется только первое слово or -c
, поэтому Bash просто запускается ls
в текущем каталоге. Остальные слова аргументы Баш, и используются для заполнения $0
, $1
и т.д.
$ bash -c $abc
'my dir'
С bash -c "$abc"
, и eval "$abc"
, есть дополнительный шаг обработки оболочки, который заставляет работать кавычки, но также вызывает повторную обработку всех расширений оболочки , поэтому существует риск случайного запуска расширения команды из предоставленных пользователем данных, если вы не очень осторожно процитировать.
Лучшие способы сделать это
Два лучших способа сохранить команду: а) использовать функцию, б) использовать переменную массива (или позиционные параметры).
Используя функцию:
Просто объявите функцию с командой внутри и запустите функцию, как если бы это была команда. Расширения в командах внутри функции обрабатываются только тогда, когда команда выполняется, а не когда она определена, и вам не нужно указывать отдельные команды.
# define it
myls() {
ls -l "/tmp/test/my dir"
}
# run it
myls
Используя массив:
Массивы позволяют создавать переменные из нескольких слов, где отдельные слова содержат пробелы. Здесь отдельные слова хранятся как отдельные элементы массива, а "${array[@]}"
расширение расширяет каждый элемент как отдельные слова оболочки:
# define the array
mycmd=(ls -l "/tmp/test/my dir")
# run the command
"${mycmd[@]}"
Синтаксис немного ужасен, но массивы также позволяют вам собирать командную строку по частям. Например:
mycmd=(ls) # initial command
if [ "$want_detail" = 1 ]; then
mycmd+=(-l) # optional flag
fi
mycmd+=("$targetdir") # the filename
"${mycmd[@]}"
или оставьте части командной строки постоянными и используйте массив, заполняющий только его часть, параметры или имена файлов:
options=(-x -v)
files=(file1 "file name with whitespace")
target=/somedir
transmutate "${options[@]}" "${files[@]}" "$target"
Недостатком массивов является то, что они не являются стандартной функцией, поэтому простые оболочки POSIX (например dash
, по умолчанию /bin/sh
в Debian / Ubuntu) их не поддерживают (но см. Ниже). Bash, ksh и zsh, однако, так что, вероятно, ваша система имеет какую-то оболочку, которая поддерживает массивы.
С помощью "$@"
В оболочках без поддержки именованных массивов все еще можно использовать позиционные параметры (псевдомассив "$@"
) для хранения аргументов команды.
Следующее должно быть переносимыми битами сценария, которые делают эквивалент битов кода в предыдущем разделе. Массив заменяется "$@"
списком позиционных параметров. Установка "$@"
выполняется с помощью set
, и двойные кавычки "$@"
важны (это приводит к тому, что элементы списка заключаются в отдельные кавычки).
Во-первых, просто сохраните команду с аргументами "$@"
и запустите ее:
set -- ls -l "/tmp/test/my dir"
"$@"
Условное задание частей параметров командной строки для команды:
set -- ls
if [ "$want_detail" = 1 ]; then
set -- "$@" -l
fi
set -- "$@" "$targetdir"
"$@"
Используется только "$@"
для опций и операндов:
set -- -x -v
set -- "$@" file1 "file name with whitespace"
set -- "$@" /somedir
transmutate "$@"
(Конечно, "$@"
обычно он заполнен аргументами самого скрипта, поэтому вам придется их где-то сохранить перед повторным рассмотрением "$@"
.)
Будьте осторожны с eval
!
Поскольку eval
вводится дополнительный уровень обработки цитат и расширения, вам нужно быть осторожным с пользовательским вводом. Например, это работает до тех пор, пока пользователь не введет ни одной кавычки:
read -r filename
cmd="ls -l '$filename'"
eval "$cmd";
Но если они дают ввод '$(uname)'.txt
, ваш сценарий успешно выполняет подстановку команд.
Версия с массивами невосприимчива к тому, что слова хранятся отдельно в течение всего времени, поэтому нет никакого предложения или другой обработки для содержимого filename
.
read -r filename
cmd=(ls -ld -- "$filename")
"${cmd[@]}"
Ссылки