Передать экранированную строку аргументов в подкоманду в оболочке Bourne - PullRequest
4 голосов
/ 02 апреля 2012

Скажем, у меня есть команда, которую я хочу выполнить (cmd), и переменная, содержащая аргументы, которые я хочу передать функции (что-то вроде --foo 'bar baz' qux). Вот так:

#!/bin/sh
command=cmd
args="--foo 'bar baz' qux"

Аргументы содержат кавычки, как показано выше, которые группируют аргумент, содержащий пробел. Затем я хотел бы запустить команду:

$command $args

Это, конечно, приводит к выполнению команды с четырьмя аргументами: --foo, 'bar, baz' и qux. Альтернатива, к которой я привык (т. Е. При использовании "$@"), представляет другую проблему:

$command "$args"

Это выполняет команду с один аргумент: --foo 'bar baz' qux.

Как выполнить команду с тремя аргументами (--foo, bar baz и qux), как предполагалось?

Ответы [ 5 ]

8 голосов
/ 02 апреля 2012

Используйте массив для точного указания списка аргументов, без разделения строк (что и делает неправильную вещь), мешая вам:

args=( --foo "bar baz" qux )
command "${args[@]}"

Если вам нужно построить список аргументов динамически, вы можете добавить к массивам с помощью +=:

args=( )
while ...; do
   args+=( "$another_argument" )
done
call_your_subprocess "${args[@]}"

Обратите внимание, что использование кавычек и [@] вместо [*] является обязательным.

5 голосов
/ 03 апреля 2012

Если вы можете выбросить текущие позиционные переменные ($1 ...), вы можете использовать следующее:

set -- '--foo' 'bar baz' 'qux'
echo "$#" # Prints "3" (without quotes)
echo "$2" # Prints "bar baz" (without quotes)
command "$@"

Только что протестировал его в скрипте #!/usr/bin/env sh, чтобы он работал по крайней мерев Dash, и должен работать в любом варианте Bourne Shell.Нет eval, необходим Python или Bash.

4 голосов
/ 02 апреля 2012

Одной из возможностей является использование eval:

#!/bin/sh

args="--foo 'bar baz' qux"
cmd="python -c 'import sys; print sys.argv'"

eval $cmd $args

Таким образом, вы заставляете интерпретировать командную строку, а не просто разбивать ее в соответствии с IFS.Это дает вывод:

$ ./args.sh 
['-c', '--foo', 'bar baz', 'qux']

Так что вы можете видеть, что аргументы переданы так, как вы хотели.

1 голос
/ 03 апреля 2012

Если у вас есть команда в форме:

args="--foo 'bar baz' qux"

и получение команды в виде массива, во-первых, не вариант, тогда вам нужно будет использовать eval, чтобы превратить ее обратно в массив:

$ args="--foo 'bar baz' qux"
$ eval "arr=($args)"

Но важно отметить, что это небезопасно , если $args предоставляется ненадежным источником, поскольку его можно использовать для выполнения произвольных команд, например, args='$(rm -rf /)'; eval "arr=($args)" заставит приведенный выше код запустить rm -rf / еще до того, как вы использовали arr.

Затем вы можете использовать "${arr[@]}", чтобы расширить его в качестве аргументов команды:

$ bash -c 'echo $0' "${arr[@]}"
--foo
$ bash -c 'echo $1' "${arr[@]}"
bar baz

или для запуска вашей команды:

"$command" "${arr[@]}"

Обратите внимание, что есть различия между ${arr[*]}, ${arr[@]}, "${arr[*]}" и "${arr[@]}", и только последний из них делает то, что вы хотите в большинстве случаев

0 голосов
/ 01 апреля 2018

Это работает для меня, изменяя IFS (внутренний разделитель полей) оболочки (чтобы содержать только символ новой строки).Вот как я переопределяю команды, которые используют аргументы в кавычках:

$ cat ~/.local/bin/make
#!/bin/sh

# THIS IS IMPORTANT! (don't split up quoted strings in arguments)
IFS="
"
exec /usr/bin/make ${@} -j6

(/bin/sh is dash)
При замене команды на echo будут использованы кавычки, поэтому будет выглядеть неправильнопри «тестировании»;но команда получает их, как и предполагалось.

Это можно проверить, заменив строку exec на

for arg in $@; do echo $arg; done
...