【问题标题】:Order of brace expansion and parameter expansion大括号展开顺序和参数展开
【发布时间】:2020-08-27 10:37:42
【问题描述】:

StackOverflow 上的一个常见比喻是:“为什么 x=99; echo {1..$x} 不起作用?” 答案是“因为大括号在参数/变量之前展开”

因此,我认为应该可以使用单个 $ 和大括号来扩展多个变量。我希望a=1; b=2; c=3; echo ${{a..c}} 打印1 2 3。首先,内大括号将扩展为${a} ${b} ${c}(在编写echo \${{a..c}} 时会这样做)。然后该结果将进行参数扩展。
但是,我得到了-bash: ${{a..c}}: bad substitution,所以{a..c} 根本没有展开。

Bash's manual 更具体一点(强调我的)。

在分割成标记后在命令行上执行扩展 [...] 展开顺序为:大括号展开;波浪号扩展、参数和变量扩展、算术扩展和命令替换(以从左到右的方式完成);分词;和文件名扩展。

注意该列表中的;,。 “从左到右的时尚”似乎适用于; 之前的整个(因此无序)列表。就像数学运算符 */ 没有优先级一样。

好的,所以大括号扩展实际上并不比参数扩展具有更高的优先级。只是{1..$x}${{a..c}}都是从左到右求值的,也就是说大括号{在参数$x之前,参数${在大括号{a..c}之前。

至少我是这么认为的。但是,当使用$ 而不是${ 时,左侧的参数会在右侧的大括号之后展开:

# in bash 5.0.3(1)
x=nil; x1=one; x2=two
echo ${x{1..2}} # prints `-bash: ${x{1..2}}: bad substitution`
echo $x{1..2}   # prints `one two`

问题

  • 可能是 bash 手册有缺陷还是我看错了?
  • 如果手册有缺陷:所有扩展的确切顺序是什么?

我只是问,因为我很好奇。我不打算在任何地方使用像$x{1..2} 这样的想法。我对解决多个变量的更好的解决方案替代不感兴趣(例如数组切片${array[@]:1:2})。我只是想更深入地了解。

【问题讨论】:

  • 我认为错误替换错误的发生是因为x{1..2} 不允许根据valid_brace_expansion_word。见subst.c:8781

标签: bash bash command-precedence


【解决方案1】:

来自:https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html

为避免与参数扩展冲突,字符串'${'不 被认为有资格进行大括号扩展,并禁止大括号扩展 直到结束的“}”。

也就是说,对于echo $x{1..2},首先发生大括号扩展,然后是参数扩展,所以我们有echo $x1 $x2。对于echo ${x{1..2}},大括号扩展不会发生,因为我们在${ 之后,还没有达到参数扩展的结束}

关于您引用的 bash 手册部分,扩展仍然存在从左到右的顺序(相对于允许的嵌套)。如果您格式化列表而不是使用,;,事情会变得更清楚:

  1. 大括号扩展
  2. 以从左到右的方式:
    波浪号扩展、参数和变量扩展、算术扩展和命令替换
  3. 分词
  4. 文件名扩展。

【讨论】:

  • 感谢您的报价。这真的很有帮助。但是,我的问题中引用的手册似乎仍然是错误的。我的解释是大括号和参数之间没有优先级。最左边的东西首先展开。然而,在$x{1..2} 中,大括号首先展开,即使左侧有一个$(因此优先于{ })。
  • 很好的解释。我没有想到对手册的这种解释。使用这种解释,参数扩展总是在命令替换之前发生。因此我希望bash -c 'echo $SECONDS $(sleep 1; echo $SECONDS) $SECONDS' 打印0 1 0。但它会打印0 1 1,表明最后一个参数扩展发生在命令替换之后。
  • 我还是不明白为什么$x{1..2}会先展开大括号。使用x=nil 和从左到右的评估,我假设$x{1..2} –param。 exp.→ nil{1..2} –brace exp.→ nil1 nil2.我知道为什么我会在我的问题中得到结果。因为扩展顺序与我假设的不同。这引出了我的问题:一般来说扩展顺序是什么?正如我们所见,它既不是严格的“变量前大括号”,也不是严格的“从左到右”
  • 因为大括号扩展确实先进行,在您引用的 bash 手册文本中,它是大括号扩展;,L-R 顺序适用于下一个,逗号分隔的扩展。
  • 非常感谢!最后,一切都为我解决了。到目前为止,我只注意到最后一个;,而忽略了“大括号扩展” 后面的;。现在一切都说得通了。
【解决方案2】:

阅读 Mo Budlong 1988 年的经典著作 Command Line Psychology,它是为常规 Unix 编写的,但其中大部分内容仍然适用于 bash。评估顺序为:

1 History substitution (except for the Bourne shell)
2 Splitting words, including special characters
3 Updating the history list (except for the Bourne shell)
4 Interpreting single and double quotes
5 Alias substitution (except for the Bourne shell)
6 Redirection of input and output (< > and |)
7 Variable substitution (variables starting with $)
8 Command substitution (commands inside back quotes)
9 File name expansion (file name wild cards) 

那么bash{1..3} 之类的代码所做的事情发生在上面的第 7 步之前,这就是 OP 代码失败的原因。

但如果必须,总是有eval,(只有在变量事先知道或首先仔细检查类型时才应使用它):

a=1; b=2; c=3; eval echo \{$a..$c}

输出:

1 2 3

【讨论】:

  • 感谢您的回答。根据这个解释,我假设bash -c 'echo `sleep 1` $SECONDS' 会打印0,因为最后一个变量会在子shell 之前 展开(规则7 在8 之前)。但是,该命令打印 1 表明变量在子shell 之后 展开。此外,该列表根本没有讨论大括号扩展。
  • @Socowi,概括来自$SECONDS 的所有变量是不正确的,因为它是一个 internal 变量,(与 user 变量相反),因此它的行为更像bash 函数。考虑以下代码,该代码将该内部变量存储在用户变量中:bash -c 'n=$SECONDS; echo $(sleep 1) $n $SECONDS' 并输出 0 1。另见:How do I use $SECONDS inside a bash script?
  • @Socowi,普通的 shell 大括号扩展将涉及 6 重定向,可能还有 8 命令替换。像bash{1..3} 这样的东西更像是5 别名替换
  • 当然bash -c 'n=$SECONDS; anyCommand; echo $n' 总是打印0,因为n 不会更新。如果在扩展顺序方面用户变量和特殊变量之间存在区别,那么在对我的问题 »所有扩展的确切顺序是什么?« 的任何回答中都已经指出了这种区别。另外,我不明白为什么brace expansion 会涉及6 重定向8 命令替换
  • @Socowi,关于 大括号扩展:对不起,我错误地使用了普通的大括号扩展这个术语来指代使用 @987654343 的命令分组@ 字符,例如 {echo foo; echo bar;} | tac.
猜你喜欢
  • 2017-06-22
  • 2020-03-23
  • 1970-01-01
  • 2020-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-02
  • 2017-11-08
相关资源
最近更新 更多