为什么 nullglob 不是默认值?

Dak*_*ron 76 shell wildcards

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

ls *
Run Code Online (Sandbox Code Playgroud)

在一个空目录中,它会将*glob扩展为一个 literal *,而不是一个空的参数列表。有一些方法可以改变这种行为,这样*在一个空目录中将返回一个空的参数列表,这看起来更直观。

那么,是否有理由nullglob默认禁用?如果是,那是什么原因?

Sté*_*las 93

nullglob选项(顺便说一句zsh,这是一项发明,仅在几年后才添加到bash( 2.0) 中)在许多情况下并不理想。这ls是一个很好的例子:

ls *.txt
Run Code Online (Sandbox Code Playgroud)

或者更正确的等价物:

ls -- *.txt
Run Code Online (Sandbox Code Playgroud)

(列出名称以 结尾的非隐藏目录的内容.txt,或具有相同名称模式的非目录文件)

随着nullglob上可以运行ls不带参数,它被视为ls -- .(列表中的当前目录),如果没有文件匹配,这可能比打电话更差ls与文字*.txt为argument¹。

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

grep foo *.txt
Run Code Online (Sandbox Code Playgroud)

foo如果没有txt文件,将在 stdin 上查找。

一个更明智的默认设置,csh、tcsh、zsh 或fish 2.3+(以及早期的 Unix shell)之一是在 glob 不匹配时完全取消命令。

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

请注意, csh 和 tcsh 与 略有不同zshfish或者bash -O failglob在以下情况下:

ls -- *.txt *.html
Run Code Online (Sandbox Code Playgroud)

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

ls -- file.txt
Run Code Online (Sandbox Code Playgroud)

您可以zsh使用以下setopt cshnullglob方法来实现该行为,但更明智的方法zsh是使用如下 glob:

ls -- *.(txt|html)
Run Code Online (Sandbox Code Playgroud)

zshand 中ksh93,您还可以在每个 glob 的基础上应用nullglob,这比修改全局设置要明智得多:

files=(*.txt(N))  # zsh
files=(~(N)*.txt) # ksh93
Run Code Online (Sandbox Code Playgroud)

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

fish2.3 之前的版本会工作,bash -O nullglob但在 glob 没有匹配时交互时会发出警告。从 2.3 开始,它的工作原理类似于,或 中zsh使用的globs 。forsetcount

现在,在历史记录上,该行为实际上Bourne shell破坏了。在先前版本的 Unix 中,globbing 是通过/etc/globhelper完成的,该 helper 的行为类似于csh:如果没有任何glob匹配任何文件,则命令将失败,否则删除不匹配的 glob。

所以我们今天的情况是由于在 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
Run Code Online (Sandbox Code Playgroud)

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


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

² 4.4 版在这方面看到了一些改进,因为set -o可以local -在 Almquist shell中将选项设置为本地函数,但这不适用于bash的第二组选项,设置为shopt

  • @JonNalley,将文件名的串联(带空格)(可能使用 `xpg_echo` 进行转换)存储到 _scalar_ 变量中。你需要类似 `readarray -td '' files < <(shopt -s nullglob; printf '%s\0' *.txt)` 和 `bash` 4.4 或更高版本或 `(shopt -s nullglob; printf ') %s\0' *.txt) | xargs -r0 cmd` 和 GNU `xargs` 可以使用任意文件名。或者,仍然使用 bash4.4,使用辅助函数使用 `local -`(从 ash 25 年后复制)作为选项的本地范围。 (6认同)