bash 变量内不尊重引用

发布于 2024-08-04 00:54:32 字数 1354 浏览 4 评论 0原文

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

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} 之间的空格。

引号和/或转义符的哪种组合允许我构建此参数列表并从此脚本执行命令?

编辑:

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

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

I'm storing the arguments to a command in a variable. The final command I want is:

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

Here's my attempt:

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

The results are:

+ 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'

As you can see, the arguments to the --define parameter are not being quoted properly. --define thinks I'm passing it only debug_package, which is incomplete.

I have tried various variations in the quotes when defining MOCK_ARGS, even trying to escape the space between debug_package and %{nil}.

What combination of quotes and/or escapes allows me to build this argument list and execute the command from this script?

EDIT:

The reason I'm storing the resulting command in a variable is because it ends up being passed into a function which does some logging, then executes the command.

Also, I have come across this FAQ which suggests I should use arrays instead of a variable. I've begun experimenting with arrays but so far don't have a working solution.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

七分※倦醒 2024-08-11 00:54:32

数组是处理此类事情的方法。这是我想到的:

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

Arrays are the way to go for things like this. Here's what I came up with:

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

Note that $* expands to all parameters seperated by spaces (suitable for passing to a log command); while "$@" expands to all parameters as separate words (as used in log_and_run, the first will be treated as the command to execute, the rest as parameters to it); similarly, "${MOCK_ARGS[@]}" expands to all of the elements of MOCK_ARGS as separate words, whether or not they contain spaces (hence each element of MOCK_ARGS becomes an element of cmd, with no quote-parsing confusion).

Also, I've quoted the expansions of MOCK_CONFIG and RESULTDIR; this isn't necessary here because they don't contain spaces, but is good habit to get in in case they ever do happen to contain spaces (e.g. is it passed into your script from outside? Then you should assume it might contain spaces).

BTW, if you need to pass additional parameters to the function (I'll use the example of the filename to log to), you can use array slicing to split off only the later parameters to use as the command to log/execute:

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

Addendum: if you want to quote space-containing parameters in the log, you can "manually" construct the log string and do whatever quoting you want. For instance:

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

Note that this will handle spaces in parameters and blank parameters, but not (for instance) double-quotes inside parameters. Doing this "right" can get arbitrarily complicated...

Also, the way I construct the log_cmd string, it winds up with a leading space; I handled this above by omitting the space before it in the echo command. If you need to actually trim the space, use "${log_cmd# }".

鲜血染红嫁衣 2024-08-11 00:54:32

Shell 脚本可能会很糟糕。在许多情况下,cmd="blah $blah";$cmdblah $blah 不同。您可以尝试eval $cmd而不是$cmd。

尝试调用此 perl 脚本而不是模拟:

#!/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

Shell scripting can be miserable. In many cases, cmd="blah $blah";$cmd differs from blah $blah. You can try eval $cmd instead of $cmd.

Try calling this perl script instead of mock:

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

You may be surprised to see what the argument list actually is:

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

Although I guess the "set -x" output is trying to show you this with the single quotes.

One nice trick I recently learned is that

function f {
  echo "$@"
}

Actually properly forwards the positional arguments to f ($* doesn't).

It looks like "eval" gives what you probably intend:

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

Output:

+ 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文