3

我将参数存储在变量中的命令中。我想要的最后一个命令是:

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}

什么样的引号和/或转义组合允许我构建这个参数列表并从这个脚本执行命令?

编辑:

我将生成的命令存储在变量中的原因是因为它最终被传递给一个执行一些日志记录的函数,然后执行该命令。

另外,我遇​​到了这个常见问题解答,它建议我应该使用数组而不是变量。我已经开始尝试使用数组,但到目前为止还没有一个可行的解决方案。

4

2 回答 2

4

Shell 脚本可能很糟糕。在许多情况下, 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
于 2009-09-04T06:52:48.017 回答
4

数组是解决此类问题的方法。这是我想出的:

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# }".

于 2009-09-06T04:25:31.003 回答