Цитирование внутри переменной bash не соблюдается

Я сохраняю аргументы команды в переменной. Последняя команда, которую я хочу:

mock -r myconfig --define "debug_package %{nil}" --resultdir results --rebuild mypackage.src.rpm

Вот моя попытка:

set -x    # for debugging

RESULTDIR=results
MOCK_CONFIG="myconfig"
MOCK_ARGS="-r $MOCK_CONFIG --define \"debug_package %{nil}\" --resultdir $RESULTDIR"
cmd="mock $MOCK_ARGS --rebuild mypackage.src.rpm"
$cmd

Результаты следующие:

+ RESULTDIR=results
+ MOCK_CONFIG=myconfig
+ MOCK_ARGS='-r myconfig --define "debug_package %{nil}" --resultdir results'
+ cmd='mock -r myconfig --define "debug_package %{nil}" --resultdir results --rebuild mypackage.src.rpm'
+ mock -r myconfig --define '"debug_package' '%{nil}"' --resultdir results --rebuild mypackage.src.rpm
ERROR: Bad option for '--define' ("debug_package).  Use --define 'macro expr'

Как видите, аргументы параметра --define не цитируются должным образом. --define думает, что я передаю только debug_package, что является неполным.

Я пробовал различные варианты кавычек при определении MOCK_ARGS, даже пытаясь избежать пробела между debug_package и %{nil}.

Какая комбинация кавычек и / или экранирования позволяет мне создать этот список аргументов и выполнить команду из этого скрипта?

РЕДАКТИРОВАТЬ:

Причина, по которой я сохраняю результирующую команду в переменной, заключается в том, что она передается функции, которая ведет журнал, а затем выполняет команду.

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


person Mike Mazur    schedule 04.09.2009    source источник
comment
Это похоже на дубликат stackoverflow.com/ questions / 954390 /   -  person Nathan Fellman    schedule 04.09.2009
comment
Ответ на stackoverflow.com/questions/954390 / у меня не работает.   -  person Mike Mazur    schedule 04.09.2009
comment
возможный дубликат Как для выполнения команды bash, хранящейся в виде строки с кавычками и звездочкой   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 05.07.2015


Ответы (2)


Массивы - лучший вариант для подобных вещей. Вот что я придумал:

log_and_run() {
    echo "$(date): running command: $*"
    "$@"
    echo "$1 completed with status $?"
}

RESULTDIR=results
MOCK_CONFIG="myconfig"
MOCK_ARGS=(-r "$MOCK_CONFIG" --define "debug_package %{nil}" --resultdir "$RESULTDIR")
cmd=(mock "${MOCK_ARGS[@]}" --rebuild mypackage.src.rpm)
log_and_run "${cmd[@]}"
# could also use: log_and_run mock "${MOCK_ARGS[@]}" --rebuild mypackage.src.rpm

Обратите внимание, что $ * расширяется до всех параметров, разделенных пробелами (подходит для передачи в команду журнала); в то время как "$ @" расширяется до всех параметров как отдельных слов (как используется в log_and_run, первое будет рассматриваться как команда для выполнения, остальные как параметры для нее); аналогично "$ {MOCK_ARGS [@]}" расширяется до всех элементов MOCK_ARGS как отдельных слов, независимо от того, содержат они пробелы или нет (следовательно, каждый элемент MOCK_ARGS становится элементом cmd без путаницы при парсинге кавычек).

Также я процитировал расширения MOCK_CONFIG и RESULTDIR; здесь нет необходимости, потому что они не содержат пробелов, но это хорошая привычка, если они когда-либо будут содержать пробелы (например, он передается в ваш скрипт извне? Тогда вы должны предположить, что он может содержать пробелы) .

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

log_and_run() {
    echo "$(date): running command ${*:2}" >>"$1" 
    "${@:2}"
    echo "$2 completed with status $?" >>"$1"
}
#...
log_and_run test.log "${cmd[@]}"

Дополнение: если вы хотите заключить в кавычки параметры, содержащие пробелы, в журнале, вы можете «вручную» создать строку журнала и сделать любое цитирование по своему усмотрению. Например:

log_and_run() {
    local log_cmd=""
    for arg in "$@"; do
        if [[ "$arg" == *" "* || -z "$arg" ]]; then
            log_cmd="$log_cmd \"$arg\""
        else
            log_cmd="$log_cmd $arg"
        fi
    done
    echo "$(date): running command:$log_cmd"
    ...

Обратите внимание, что это будет обрабатывать пробелы в параметрах и пустые параметры, но не (например) двойные кавычки внутри параметров. Выполнение этого «правильного» действия может быть сколь угодно сложным ...

Кроме того, как я строю строку log_cmd, она заканчивается пробелом в начале; Я обработал это выше, опустив пробел перед ним в команде echo. Если вам действительно нужно обрезать пространство, используйте "${log_cmd# }".

person Gordon Davisson    schedule 06.09.2009
comment
Спасибо, это решает мою проблему! Гениальным ходом было преобразовать полную команду mock в массив. Это привело к рефакторингу моей log_and_run() функции, которая наивно использовала $1 для регистрации и выполнения команды вместо $* и / или $@. Мне не удалось найти цитаты вокруг "debug_package %{nil}", чтобы они отображались в журналах, но это небольшая деталь, с которой я могу смириться. - person Mike Mazur; 06.09.2009

Создание сценариев оболочки может быть неприятным. Во многих случаях cmd="blah $blah";$cmd отличается от blah $blah. Вы можете попробовать eval $cmd вместо $ cmd.

Попробуйте вызвать этот сценарий perl вместо mock:

#!/usr/bin/perl
print join("\n",@ARGV),"\n";

Вы можете быть удивлены, увидев, что на самом деле представляет собой список аргументов:

-r
myconfig
--define
"debug_package
%{nil}"
--resultdir
results
--rebuild
mypackage.src.rpm

Хотя я предполагаю, что вывод "set -x" пытается показать вам это с помощью одинарных кавычек.

Я недавно усвоил один приятный трюк:

function f {
  echo "$@"
}

На самом деле правильно пересылает позиционные аргументы в f ($* этого не делает).

Похоже, что "eval" дает то, что вы, вероятно, имеете в виду:

set -x    # for debugging
RESULTDIR=results
MOCK_CONFIG="myconfig"
MOCK_ARGS="-r $MOCK_CONFIG "'--define "debug_package %{nil}" --resultdir '"$RESULTDIR"
cmd="mock $MOCK_ARGS --rebuild mypackage.src.rpm"
echo $cmd
eval $cmd

Вывод:

+ echo mock -r myconfig --define '"debug_package' '%{nil}"' --resultdir results --rebuild mypackage.src.rpm
mock -r myconfig --define "debug_package %{nil}" --resultdir results --rebuild mypackage.src.rpm
+ eval mock -r myconfig --define '"debug_package' '%{nil}"' --resultdir results --rebuild mypackage.src.rpm
++ mock -r myconfig --define 'debug_package %{nil}' --resultdir results --rebuild mypackage.src.rpm
-r
myconfig
--define
debug_package %{nil}
--resultdir
results
--rebuild
mypackage.src.rpm
person Jonathan Graehl    schedule 04.09.2009
comment
Спасибо - eval был для меня быстрым решением. - person John Lehmann; 11.09.2011