如果不存在 .txt 文件,则在 shell 中使用 *.txt 进行扩展不起作用

An *_*rer 10 command-line bash

我正在玩扩展,我注意到一个奇怪的行为。我试着做:

echo ./*.txt
Run Code Online (Sandbox Code Playgroud)

我的当前目录中没有任何 .txt 文件。我得到的输出是:

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

我只是好奇:我为什么会得到这个?我原以为不会得到任何输出。

PS:当我有一个.txt文件时,扩展被正确解释。换句话说,假设我有一个文件,smthn.txt,回声实际上是 echo current_directory/smthn.txt

Col*_*inB 15

改编自 bash shell 手册页,

bash 扫描每个单词中的字符 *、? 和 [。如果出现这些字符中的一个,则该单词被视为一个模式,并替换为按字母顺序排序的与该模式匹配的文件名列表。如果未找到匹配的文件名,并且未启用 shell 选项 nullglob,则该词保持不变。如果设置了 nullglob 选项,但未找到匹配项,则删除该词。

在这种情况下,我假设 nullglob 没有启用,所以这个词保持不变 - 因此你看到的输出。

  • 您可以按如下方式更改行为:`shopt -s nullglob` 将为不匹配的模式生成空字符串,而`shopt -u nullglob`(标准设置)将生成模式本身。 (2认同)

Eli*_*gan 14

我原以为不会得到任何输出。

如果nullglob是默认值,许多命令的行为会非常出乎意料,因为(也许不幸的是)命令以与一个或多个文件名参数的情况不同的方式处理文件名参数的情况很常见。

假设您已启用nullglob( shopt -s nullglob) 并且您在一个没有文件匹配的目录中*.txt。然后*.txt确实会扩展为空 - 不是空字段,但根本没有字段 - 正如您所期望的那样。但这会产生以下结果:

  • ls *.txt将列出当前目录中的所有文件(隐藏文件除外),因为ls当您不传递任何文件名参数时会这样做。
  • cat *.txt会从标准输入中读取,因为当cat没有文件名参数时,就好像你运行了cat -. 如果以交互方式运行,它会等待输入。许多命令的行为方式都是如此。
  • cp *.txt dest/会因错误而失败cp: missing destination file operand after 'dest/'。这不是一场灾难,但它令人困惑,并且与可能期望的无声成功大不相同。
  • file *.txt,以及其他各种在文件名参数为零的情况下没有特殊行为的程序,当没有传递时,仍然会失败并显示错误或使用消息。
  • 即使直觉上觉得它们应该起作用的情况通常也不会。printf 'Got file: "%s"\n' *.txt会打印Got file: ""而不是什么都不打印。
  • 意外失败引用的出现*?[打算由外壳扩展将更加频繁产生明显错误的结果,但方式可能难以弄清楚。例如,如果当前目录中没有以 开头的文件名gedit,那么apt list gedit*apt list 'gedit*'预期的位置)将变得只是apt list并列出所有可用的包。

所以最好不要在没有请求的情况下得到这种行为。实际上被简化的最常见的实际情况可能nullglobfor f in *.txt。另请参阅此问题Sergiy Kolodyazhnyy 的回答与此相关)。

更难回答的问题是为什么failglob-- 具有不匹配任何文件的 glob 是扩展错误的地方 -- 不是 bash 中的默认值。我相信Sergiy Kolodyazhnyy 的回答即使没有直接解决它也能抓住原因。在不产生扩展错误的情况下保留未扩展的 glob 是(也许不幸的是)标准化行为,它也是传统的,因此是预期的行为。尽管 bash 不会尝试完全符合 POSIX,除非使用名称调用sh或传递--posix选项,但它的许多设计选择即使不在 POSIX 模式下也直接遵循 POSIX。他们不得不选择一些行为,而且违背用户的期望也会带来不利影响。


我认为这是这件事对历史影响最小的方面,所以我把它留到最后……但值得一提的是,nullglob行为在概念上有点奇怪。

nullglob起初看起来很优雅,因为在语法上,它处理零匹配文件的情况与一、二或任何其他数字的情况没有区别。我们运行的命令,其中 globs 扩展为参数,并不倾向于将它们视为相同,如上所述。但从句法上来说,这至少感觉是对的,我认为这是您提出问题的动机。

然而,还有另一个更微妙的不一致nullglob没有解决——它实际上被放大了。的情况下,零名的通配符(“通配符”)是从一个或两个,或任何其他数量的处理过的深刻不同。例如,与shopt -s nullglob,如果ab?d?f不匹配任何文件,则将其删除;如果ab?d不匹配任何文件,则将其删除;但是如果ab不匹配任何文件(即,如果没有名称恰好是 的文件ab),它仍然不会被删除。当然,如果将其删除将是一场灾难,因为它可能根本不打算引用当前目录中的现有文件;它甚至可能不引用文件。但这仍然消除了对完全一致性的任何希望。

bash 提供的三种行为——默认将不匹配任何文件的 glob 视为它们不是 glob 并在未扩展的情况下传递它们,这是您期望处理它们的行为(如果您能原谅这种奇怪的短语)作为表示匹配 ( nullglob) 的所有文件的所有零,以及将它们视为错误 ( failglob)的安全行为- 都代表不同的方法来解决 shell 中固​​有的歧义,无法知道是否有任何特定的单词打算成为一个文档名称。shell 在不知道你用它调用的特定命令将如何处理它们的参数的情况下执行它的扩展。

这是关注点分离的众多实例之一。在设计遵循 Unix 哲学的系统中,每个部分都旨在做一件事并把它做好。shell 将文本处理为命令和参数并调用这些命令,其中大部分命令在 shell 本身之外。这往往比外部命令本身负责执行这些转换的系统(如 DOS 和 Windows 中的传统命令处理器)更好和更通用。但它偶尔也有缺点。


Ser*_*nyy 6

主要原因是因为这是POSIX指定的标准行为- 该标准涵盖了 shell 命令语言和其他模式匹配(shell 等bashdashshell - Ubuntu 的默认值/bin/sh,并ksh遵循此标准)。从第2.13.3 节用于文件名扩展的模式

如果模式与任何现有文件名或路径名都不匹配,则模式字符串应保持不变。

这当然有一个副作用 - 匹配的文件名可能是*.txt. 该nullglob选件bashzsh可以帮助:如果该选项是通过支持shopt -s nullglob(这不是默认它适用于这个问题启用),然后globstar将扩大到空字符串时,没有匹配的文件名中。ksh93有自己先进的模式匹配机制,达到同样的效果~(N)*.txt

另请参阅为什么 nullglob 不是默认值?