如果不存在.txt文件,则无法在外壳中使用* .txt进行扩展


10

我在玩扩展,发现一个奇怪的行为。我试着做:

echo ./*.txt

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

./*.txt

我很好奇:我为什么得到这个?我期望没有任何输出。

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

Answers:


15

改编自bash shell手册页,

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

在这种情况下,我假定未启用nullglob,因此该词保持不变-因此您会看到输出。


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

13

我期望没有任何输出。

如果nullglob使用默认值,那么许多命令的行为将非常出乎意料,因为(很不幸),命令对待文件名参数的情况的定性不同于一个或多个文件名参数的情况。

假设您已启用nullglobshopt -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的答案该问题相关联)。

较难回答的问题是,为什么failglobbash中的默认值不是扩展名,即它与任何文件都不匹配是一个扩展错误。我相信Sergiy Kolodyazhnyy的答案即使没有直接解决也能抓住原因。保留未扩展的球体而不会产生扩展错误是(很不幸)是标准化的行为,也是传统的行为,因此也是预期的行为。尽管bash除非使用名称进行调用sh或通过该--posix选项,否则它不会尝试完全兼容POSIX ,但即使不是在POSIX模式下,它的许多设计选择也直接遵循POSIX。他们必须选择一些行为,并且有违背用户期望的缺点。


我认为这是该问题历史上影响最小的方面,因此我将其保留了下来……但是值得一提的是,nullglob行为在概念上有些奇怪。

nullglob乍一看似乎很优雅,因为在语法上,它对待零匹配文件的情况与一,二或任何其他数字的情况没有区别。这水珠扩展到论据,我们运行的命令,倾向于把它们相同,上面详细介绍。但是从句法上讲,这至少是正确的,我认为这是提出问题的动机。

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

bash提供了三种行为-默认情况下,将与任何文件都不匹配的glob视为不是glob,并将它们传递为未扩展状态,这是您期望对其进行处理的行为(如果您可以避免使用这种奇怪的短语)表示匹配的所有文件均为零(nullglob),以及将其视为错误的安全行为(failglob),都代表了不同的方法来解决shell固有的歧义性,从而无法知道是否有任何特定的单词打算用作文件名。Shell在执行扩展时不知道您使用它调用的特定命令将如何处理其参数。

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


显然,bash-4.3.39(2)没有failglob。因此它不能成为默认值,因为并不总是支持它。
罗斯兰

6

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.