在大多数 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
或相反zsh
,bash
不支持选项的本地范围³,该选项在全局启用时确实会破坏一些东西,例如 bash-completion 函数)。
zsh
请注意,csh 和 tcsh 与,fish
或bash -O failglob
在以下情况下略有不同:
ls -- *.txt *.html
您需要所有的全局变量都不匹配才能取消命令。例如,如果有一个 txt 文件而没有 html 文件,则变为:
ls -- file.txt
你可以通过 with 来获得这种行为zsh
,set -o cshnullglob
但更明智的方法zsh
是使用像这样的 glob:
ls -- *.(txt|html)
在zsh
和中ksh93
,您还可以申请空球在每个全局的基础上,这是比修改全局设置更明智的方法:
files=( *.txt(N) ) # zsh
files=( ~(N)*.txt ) # ksh93
如果没有txt
文件,则会创建一个空数组,而不是因错误而导致命令失败(或*.txt
使用其他 shell 将其设为带有一个文字参数的数组)。
fish
2.3 之前的版本的工作方式与此类似bash -O nullglob
,但在交互时当 glob 不匹配时会发出警告。从 2.3 开始,它的工作方式与,或zsh
中使用的 glob 不同。for
set
count
现在,在历史记录中,这种行为实际上是破碎的由 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
的等效选项命名,尽管bash
有allow_null_glob_expansion
多变的对于早期版本
² 可能ls
会报告文件不存在的错误*.txt
,除非该文件已在时间间隔内创建,或者当前目录恰好是可搜索但不可读取且该文件或目录存在。mkdir -p '*.txt/wtf'; chmod a=,u=wx .
例如尝试之后
3 版本 4.4 在这方面看到了一些改进,因为set -o
可以将由设置的选项设置为本地函数,就像在 Almquist shell 中一样,但这对于的第二组选项(用设置的选项)local -
不起作用bash
shopt
答案2
此设计采用了一种安全方法,考虑使用nullglob
为在提供 void 参数时产生 void 结果的命令的默认值。但是,这种默认行为对于诸如 之类的命令可能并不理想ls
。虽然nullglob
在脚本中证明很有用,但它并不完全安全,除非ls
用函数替换,如下所示:
myls() { (($#)) && ls "$@" ; }
这可以确保使用 nullglob 时获得所需的结果:
shopt -s nullglob
myls empty_directory/*
设计语言默认值时,优先考虑的是采用尽量减少意外行为的选项。优先选择例外而不是不可预测的行为。
必须注意的是,引入nullglob
具有不可预测的输入变化的复杂脚本可能会使其不稳定。在实践中,这可能被认为是一个看似不错的想法,但实际上需要额外的时间进行预防性调试。