为什么 nullglob 不是默认值?

为什么 nullglob 不是默认值?

在大多数 shell 中nullglob这不是默认设置。这意味着,例如,如果您运行此命令

ls *

在空目录中,它将把*glob 扩展为文本*,而不是空参数列表。有多种方法可以更改该行为,以便*在空目录中返回空参数列表,这看起来更直观。

nullglob那么,默认情况下禁用是否有原因?如果是这样,原因是什么?

答案1

nullglob在许多情况下,该选项并不理想。这ls是一个很好的例子:

ls *.txt

或者更正确的等价物:

ls -- *.txt

(列出名称以以下结尾的文件,.txt类型除外目录它列出了它们的内容;也许您打算使用该-d选项来避免特殊待遇目录文件)。

如果没有文件匹配,则 onnullglob将在没有参数的情况下运行ls,该参数被视为ls -- .(列出当前目录的内容),这可能比ls使用文字*.txt作为参数进行调用更糟糕。

大多数文本实用程序都会遇到类似的问题:

grep foo *.txt

foo如果没有文件,将在标准输入上查找txt

一个更明智的默认设置,即 csh、tcsh、zsh 或 Fish 2.3+(以及早期 Unix shell)的默认设置是,如果 glob 不匹配,则完全取消该命令。

bash(从版本 3 开始)有一个failglob选项(这个讨论很有趣,因为与ash、 AT&T ksh或相反zshbash不支持选项的本地范围³,该选项在全局启用时确实会破坏一些东西,例如 bash-completion 函数)。

zsh请注意,csh 和 tcsh 与,fishbash -O failglob在以下情况下略有不同:

ls -- *.txt *.html

您需要所有的全局变量都不匹配才能取消命令。例如,如果有一个 txt 文件而没有 html 文件,则变为:

ls -- file.txt

你可以通过 with 来获得这种行为zshset -o cshnullglob但更明智的方法zsh是使用像这样的 glob:

ls -- *.(txt|html)

zsh和中ksh93,您还可以申请空球在每个全局的基础上,这是比修改全局设置更明智的方法:

files=( *.txt(N)  ) # zsh
files=( ~(N)*.txt ) # ksh93

如果没有txt文件,则会创建一个空数组,而不是因错误而导致命令失败(或*.txt使用其他 shell 将其设为带有一个文字参数的数组)。

fish2.3 之前的版本的工作方式与此类似bash -O nullglob,但在交互时当 glob 不匹配时会发出警告。从 2.3 开始,它的工作方式与,或zsh中使用的 glob 不同。forsetcount

现在,在历史记录中,这种行为实际上是破碎的由 Bourne shell 实现。在以前的 Unix 版本中,通配符是通过/etc/glob帮助程序完成的,该帮助程序的行为如下csh:如果所有通配符均未与任何文件匹配,则命令将失败,否则将删除没有匹配的通配符。

所以我们今天的情况是由于 Bourne shell 中做出的错误决定造成的。

请注意,Bourne shell(和 C shell)附带了另一个新的 Unix 功能:环境。这意味着变量扩展(它的前身只有$1, $2... 位置参数)。 Bourne shell 还引入了命令替换。

Bourne shell 的另一个糟糕的设计决策是在变量扩展和命令替换时执行通配符(和分割)(可能是为了向后兼容 Thompson shell,如果包含通配符,echo $1仍然会调用它(它更像是预处理器宏扩展)在那里,正如扩展值再次被解析为 shell 代码))。/etc/glob$1

例如,不匹配的失败的 glob 意味着:

pattern='a.*b'
grep $pattern file

将使命令失败(除非a.whateverb当前目录中有一些文件)。csh(它也在变量扩展时执行通配符)在这种情况下确实会使命令失败(我认为这比在那里留下一个休眠的错误更好,即使它不如根本不进行通配符,就像在rc// zsh... fish)。


1996 年在 2.0 中添加了shopt内置函数,以zsh的等效选项命名,尽管bashallow_null_glob_expansion 多变的对于早期版本

² 可能ls会报告文件不存在的错误*.txt,除非该文件已在时间间隔内创建,或者当前目录恰好是可搜索但不可读取且该文件或目录存在。mkdir -p '*.txt/wtf'; chmod a=,u=wx .例如尝试之后

3 版本 4.4 在这方面看到了一些改进,因为set -o可以将由设置的选项设置为本地函数,就像在 Almquist shell 中一样,但这对于的第二组选项(用设置的选项)local -不起作用bashshopt

答案2

此设计采用了一种安全方法,考虑使用nullglob为在提供 void 参数时产生 void 结果的命令的默认值。但是,这种默认行为对于诸如 之类的命令可能并不理想ls。虽然nullglob在脚本中证明很有用,但它并不完全安全,除非ls用函数替换,如下所示:

myls() { (($#)) && ls "$@" ; }

这可以确保使用 nullglob 时获得所需的结果:

shopt -s nullglob
myls empty_directory/*

设计语言默认值时,优先考虑的是采用尽量减少意外行为的选项。优先选择例外而不是不可预测的行为。

必须注意的是,引入nullglob具有不可预测的输入变化的复杂脚本可能会使其不稳定。在实践中,这可能被认为是一个看似不错的想法,但实际上需要额外的时间进行预防性调试。

相关内容