首页 > 解决方案 > bash 是否在 ${var+...} 内错误地执行“$@”扩展?

问题描述

我目前得到 3 个 shell 的 2 个意见:

$ bash -c 'set bar; set foo${1+ "$@"}; echo "$# $*"'
1 foobar

$ ash -c 'set bar; set foo${1+ "$@"}; echo "$# $*"'
2 foo bar

$ dash -c 'set bar; set foo${1+ "$@"}; echo "$# $*"'
2 foo bar

或者我是否忽略了一些将我的示例呈现为实现定义的行为的 POSIX 定义?

请注意,似乎只有“$@”会触发差异。以下对所有 3 个 shell 的工作方式相同:

$ bash -c 'set bar; set foo${1+ $*}; echo "$# $*"'
2 foo bar

$ ash -c 'set bar; set foo${1+ $*}; echo "$# $*"'
2 foo bar

$ dash -c 'set bar; set foo${1+ $*}; echo "$# $*"'
2 foo bar

不幸的是,如果参数应该包含空格,则 $* 与 "$@" 并不完全相同。

顺便说一句,我使用的是 Bash 版本 4.4.12(1)-release。

标签: bashshellscriptingposixexpansion

解决方案


虽然这看起来确实是一个错误,但我之前遇到过一些带有“... $@”的奇怪边缘情况,这符合预期的行为,至少从历史上看是这样。" $@"(带有悬挂空白)是未定义的行为,因为您基本上要求将$@数组的所有成员视为 shell 解析器的 args加上一个空白字符。你可能有点难以理解,而且你所说的一些事情具有误导性,所以我想我会提供更多信息和提示以实现更好的行为。

您使用 "$@" 并尝试将其与 "$*" 互换的方式会给您带来问题。与其预期用途相匹配的行为$*之间存在显着差异。$@如果你使用$@,那是因为你想做这样的事情:

wrapper_fxn() {
  wrapped_cmd "$@"
}

当您将 $@ 括在引号中(以及任何其他数组)时,bash 会评估它,就好像数组的每个参数/成员首先用双引号括起来一样。但是,这涉及到更多的魔法,因为它实际上并没有将其包装在双引号中,而是删除了双引号,但表示在稍后的步骤中更改解析行为。

存在的特殊行为的原因"$@"是帮助处理令人沮丧quotingargv干扰的问题。

所以,这样想它们:

  • $@ : 多个 args / 用于传播 args,同时保留空格的引用
  • $* : 单个 arg / 用于将数组中的所有字符串连接为单个 arg

我的建议:不要这样做"$@ ";这是未定义的行为。只要你引用 like "$@",你就在尝试进行参数扩展,并添加另一个参数或参数数组,就像这样:

# Making sure to have quotes around each new arg
somecmd "$newFirstArg" "$@" "${additionalArray[@]}" "$newLastArg"

...它将更好地传达意图,并且几乎不会产生意外行为。

更深入

话虽如此,“$@”的奇怪边缘行为在您使用它时必须这样做:"... $@",就像“$@”一样。Bash 将合并数组中的一项,具体取决于...带有额外字符的位置。

$@当您在引用语句的右侧或左侧添加任何内容时,例如 "extra stuff $@"or "$@ extra stuff",bash 所做的只是将 分别添加extra stuff到数组的第一个或最后一个成员上。例外情况是,如果双引号语句中有另一个数组,可能$@像这样重复:"$@ $@". 它实际上会将第一个数组的最后一项与最后一个数组的第一项合并。所以,如果$@有 3 个成员,final argv[]of"$@ $@"将有 5 个 args,而不是 6 个。

这是一个实用功能,可帮助您了解该行为:

# example function
print-args () 
{ 
    local i=0;
    for arg in "$@";
    do
        printf "[${i}]:%s\n" "\"$arg\"";
        let i++;
    done
}

# good example arg-set covering many of the important edge cases
set "one \"one" two three

# One case, modify it to see the various permutations of the edge cases
print-args " $@ $@ "
#>[0]:" one "one"
#>[1]:"two"
#>[2]:"three one "one"
#>[3]:"two"
#>[4]:"three "


推荐阅读