Bash内置命令中类似"a=b"的参数中的文件名扩展

7

我了解到在Bash中运行命令时,文件名扩展是在命令执行之前完成的。但是当尝试使用以下命令(带有-x选项):

touch foo=3    # Create a file with name "foo=3"
+ touch foo=3
declare foo=?
+ declare 'foo=?'
alias foo=*
+ alias 'foo=*'

我没有得到预期的结果,因为foo=?和foo=*没有被扩展为文件名"foo=3":

declare -p | grep 'foo='    # => foo='?'
alias | grep 'foo='         # => alias foo='*'

但如果我运行另一个内置命令,比如cd,或者自己编写的接受赋值作为参数的函数 show_rhs() { echo "${1%=*}='${1#*=}'"; },我将得到我期望的结果(foo=? 和 foo=* 将被扩展)。

cd foo=?            # => foo=3: Not a directory
show_rhs() foo=*    # => foo='3'

我只看到这里的区别是“declare”和“alias”内置并接受参数作为赋值。根据-x选项的输出,似乎在文件名扩展之前添加了一对引号来包含分配。
但是,如果无论命令是什么,文件名扩展都会在命令执行之前运行,则传递给declare和alias的参数应该是foo = 3而不是foo =?和foo = *,因为存在文件“foo = 3”。
因此,Bash是否对“a = b”之类的参数进行特殊处理(可能是引用通配符),具体取决于文件名扩展之前的命令?
(我的环境:CentOS 5.8 64位,GNU Bash 3.2.25)

别名和变量存在不同的命名空间。您可以绝对拥有一个与变量同名的别名。使用普通赋值创建的变量和使用declare创建的变量是相同的,因此foo=3; declare foo=?会让您得到一个值为?的变量foo。您确定您会得到您声称的那两行错误吗?因为我不知道这是如何可能的。这两种情况都不会评估变量(第二种情况甚至不是有效的shell行,并且应该给您一个关于意外标记的错误)。 - Etan Reisner
抱歉造成困惑。我并不是指是否可能创建一个变量和别名使用相同的名称。我的意思是,由于存在文件“foo=3”,因此应将foo=?和foo=*都扩展为foo=3,然后再传递给这些命令,因此变量和别名都应该具有值“3”,而不是原始通配符。声明和别名的代码只是示例。 - ebk
啊,好的。我差点评论了一个 foo=3 文件,但我认为这不太可能(而且你在帖子中没有提到)。但是,我认为你的猜测很有可能是正确的。这些内置函数直接接受赋值的事实意味着否则正常的 glob 扩展行为在那里不会发生。(对于 foo=? 也不会发生。)POSIX 规范中可能有关于这个的内容。我可以稍后尝试查找。 - Etan Reisner
我在第一个代码块的第一行放了一行"> foo=3" :) 但是我认为我应该改为“touch foo=3”,并加上一些解释性注释以使其更清晰。谢谢。 - ebk
1个回答

2
Bash解析其内置命令的某些部分的方式不是严格按照Posix规范,也没有很好的文档记录。特别地,在接受此类参数的命令中(aliasdeclareexportlocalreadonlytypeset),赋值参数并不受路径扩展或单词分割的影响。(这是通过抑制扩展来内部实现的,而不是引用元字符,尽管不容易看到实现细节可能如何变得可见。)即使在Posix模式或者作为sh启动bash,这种情况也会发生。请注意,路径名扩展的抑制仅适用于看起来像赋值语句的参数。扩展问题示例:
touch foo=3    # Create a file with name "foo=3"
+ touch foo=3
declare foo=?
+ declare 'foo=?'

bar="foo=?"    # Put the declare argument in a variable
+ bar='foo=?'
declare $bar
+ declare foo=3

正如预期的那样,dash路径名展开并将参数分割为aliasexport,与Posix规范一致。显然,zsh也是这样做的。
除了在Posix模式下,bash还会展开看起来像赋值语句的参数右侧的波浪线。在Posix模式下,它仅限于列出的内置赋值参数,尽管Posix指定在命令单词之前的变量赋值中仅在=后进行波浪线展开。这就是dash的做法,但zsh将其扩展到“typeset家族的命令”(在zsh手册中有记录)。

@ebk:你引用的页面明确表示变量赋值“在命令名称之前”,这也是我所说的。 (这包括没有命令词的赋值。) - rici
抱歉,我不小心删除了之前的评论,因为我还没有习惯使用stackoverflow。请再次发布页面链接作为其他人的参考。除了没有命令单词的赋值之外,我无法想象如何在简单命令中将变量赋值放在命令名称之前。你能给我一个例子吗? - ebk
2
@ebk:如果在命令词之前放置一个赋值语句,则该赋值仅适用于传递给该命令的环境。最常见的例子可能是 IFS=,read -r a b c,其中对 IFS 的赋值在脚本中不可见,但是是 read 命令使用的环境的一部分。在苹果shell脚本入门指南中有更长的解释和示例,https://developer.apple.com/library/mac/documentation/OpenSource/Conceptual/ShellScripting/shell_scripts/shell_scripts.html#//apple_ref/doc/uid/TP40004268-CH237-SW12 - rici
谢谢你的示例。我不知道在Bash中将赋值放在命令之前是合法的。我会进行一些研究。 - ebk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接