【问题标题】:How does and why does bash split "{xs[@]}" into arguments?bash 如何以及为什么将“{xs[@]}”拆分为参数?
【发布时间】:2014-03-27 13:42:09
【问题描述】:

我对 bash 如何将一行拆分为程序的参数有基本的了解,并且足以避免包含空格的参数出现问题,但我想更进一步,了解正在发生的事情和原因。大多数指南告诉你做什么,但不告诉你为什么。一些例子可能有助于解释......

我将使用这个简短的 Python 脚本来转储参数列表:

#!/usr/bin/env python
import sys
print sys.argv[1:]

我们称之为“dumpargs”。 (你可以用 C 甚至 bash 编写它,但 Python 已经足够简洁了,我不想通过额外的 bash 解释和扩展字符串层来混淆问题。)

首先,一些简单的例子:

$ dumpargs foo bar baz
['foo', 'bar', 'baz']
$ dumpargs "foo bar" baz
['foo bar', 'baz']

好的,太好了。我们可以使用引号来传递一个包含空格的参数,方法是用引号括起来。但我们不限于将引号放在参数的外部。如果我们把它们放在中间呢?

$ dumpargs foo" "bar
['foo bar']
$ dumpargs foo" "bar" "baz xyzzy
['foo bar baz', 'xyzzy']

好的,很酷。我认为这表明引号只是修改了空格的解释方式。双引号之间出现的空格不是参数分隔符。不带引号的空格变成分隔符,带引号的空格变成真正的空格,引号消失。

数组呢?

$ xs=(one two "buckle my shoe")

$ dumpargs ${xs[*]}
['one', 'two', 'buckle', 'my', 'shoe']
$ dumpargs ${xs[@]}
['one', 'two', 'buckle', 'my', 'shoe']
$ dumpargs "${xs[*]}"
['one two buckle my shoe']
$ dumpargs "${xs[@]}"
['one', 'two', 'buckle my shoe']

显然,这四个中的最后一个是最常用的,并且很可能是我们希望在我们的数组表示文件名列表的地方使用的。其他人都将"buckle my shoe" 中的空格与数组元素之间的分隔符混淆了。但它实际上在做什么?它看起来像是由变量扩展和引用操作组成。是吗?或者 bash 是否只是对在数组扩展周围立即看到双引号的情况进行特殊处理?

这里有更多示例来尝试测试发生了什么:

$ xs=(one two "buckle my shoe")

$ dumpargs "${xs[@]} stop"
['one', 'two', 'buckle my shoe stop']
$ dumpargs "${xs[@]} and ${xs[@]}"
['one', 'two', 'buckle my shoe and one', 'two', 'buckle my shoe']

我认为这至少表明它不仅仅是直接围绕数组扩展的一对引号的特殊情况。数组扩展会产生某种类似字符串的输出,而引号会影响将类似字符串的东西转换为参数序列的方式。但它不仅仅是一个普通的字符串,因为它里面有两种不同的类似空间的东西。它具有某种“参数分隔符”,而不考虑引号将继续成为参数分隔符,但它也具有“诚实的善良空间”,如果它们被引号包围,则不会成为参数分隔符。相比之下,${xs[*]} 输出的常规字符串只有“诚实的空格”,没有特殊的“参数分隔符”。

这是理解它的好方法吗?有没有更好的方法来理解 bash 如何以及何时将数组呈现为字符序列,以及它如何以及何时拆分参数?

【问题讨论】:

  • @devnull:想解释一下你的评论吗?
  • @AaronDigulla 如果 OP 确实做了很多研究,说 set -xecho 而不是 dumpargs 会解释很多。也就是说,我在这里看到了 2 个问题:(1)@* 之间的区别,(2)引用变量的影响;这两个都问过很多次了。
  • @devnull 不。确实,我并不需要知道这一点来解决眼前的问题,但我认为这是“给某人一条鱼,他们“吃一天,教他们钓鱼,吃一辈子”。我想要更深入的了解,但我发现很难找到除了“给我一条鱼”以外的其他功能。
  • @glennjackman 谢谢!以“数组的任何元素......”开头的段落确实准确地解释了它。我只是没有找对地方。我感觉没有我希望的那么聪明,但这正是我提出的问题的答案,并且来自最权威的来源。如果作为答案发布,我会接受。

标签: bash


【解决方案1】:

这种行为的根源可能是旧的“将参数传递给子shell”问题。一开始,$* 一直有效,直到您开始在参数中使用空格。

 Input         Subshell sees
 a b           "a" "b"
 "a b"         "a" "b"
 a b\ c        "a" "b" "c" 
 a b\\\ c      "a" "b c" 

我们可以引用$*,但这会将所有参数合并到一个字符串参数中(即子shell 总是会看到"a b""a b c")。显然,这不好。

因此引入了@ 表单。如果没有引号,$*$@ 的行为相似。带引号 - "$@" - 扩展为正确引用的参数列表。

当 KSH / BASH 引入数组时,它们保持对称性(没有$*,您无法将数组变成单个字符串)。

相关:

【讨论】:

  • 我喜欢这个,因为它提供了有用的历史和上下文,但我认为这仍然不足以真正理解当其他任何内容与 $@ 一起出现在引号内时发生了什么。我理解 "$@" 作为一个组合单元的含义。我仍然对 $@ 单独或在更大的引用结构中的含义知之甚少。我有一个假设,在问题中进行了描述,并且与我观察到的一致,但这是真的吗?
  • $@ 检查它是否被引用,如果是,它会在数组元素的正确引用序列中展开。如果您查看解析器的源代码,您会发现它实际上在输入中寻找"$@" 作为标记(在git.savannah.gnu.org/cgit/bash.git/tree/parse.y 中搜索select_command:
  • 每一行的解析也在那个文件中,read_token_word() 是引号的处理,我认为隐藏在某处的还有"$@" 的处理。
  • grumble re:链接 ABS(就像 W3Schools 对 Javascript 的抨击一样——一个过时的资源,充满了不良做法的例子,但有很多谷歌果汁)。作为替代方案,请考虑BashFAQ #5BashGuide on ArraysBash-Hackers' Wiki on Arrays。或者当然是manual
  • @CharlesDuffy 谢谢,已修复。
猜你喜欢
  • 1970-01-01
  • 2011-05-15
  • 1970-01-01
  • 2022-06-15
  • 1970-01-01
  • 2023-02-24
  • 2020-01-05
  • 1970-01-01
  • 2011-07-06
相关资源
最近更新 更多