我在尝试扩展时,注意到了一种奇怪的行为。我尝试这样做:
echo ./*.txt
并且我当前目录中没有任何 .txt 文件。我得到的输出是:
./*.txt
我只是好奇:为什么我会得到这个?我原本以为不会得到任何输出。
PS:当我有一个.txt
文件时,扩展被正确解释。换句话说,假设我有一个文件,,smthn.txt
回显实际上回显了current_directory/smthn.txt
。
答案1
改编自 bash shell 手册页,
bash 扫描每个单词以查找字符 *、? 和 [。如果出现其中一个字符,则该单词被视为模式,并替换为按字母顺序排列的与该模式匹配的文件名列表。如果没有找到匹配的文件名,并且未启用 shell 选项 nullglob,则该单词保持不变。如果设置了 nullglob 选项,并且未找到匹配项,则删除该单词。
在这种情况下,我假设 nullglob 未启用,所以单词保持不变 - 因此您看到的输出。
答案2
我原本以为不会得到任何输出。
如果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: ""
而不是什么都没有。 - 意外未能引用
*
、?
和[
不是shell 打算扩展的更经常会产生明显错误的结果,但可能很难弄清楚。例如,如果当前目录中没有以 开头的文件名gedit
,那么apt list gedit*
(whereapt list 'gedit*'
本来是打算的)将变成只是apt list
列表全部可用的包。
因此,最好不要在没有请求的情况下获得此行为。 可能最常见的实际情况实际上是通过 简化的nullglob
。for f in *.txt
另请参阅这个问题(哪个Sergiy Kolodyazhnyy 的回答链接到)。
更难回答的问题是为什么failglob
--如果 glob 不匹配任何文件,则会出现扩展错误--这不是 bash 中的默认设置。我相信Sergiy Kolodyazhnyy 的回答即使没有直接解决它,也能抓住原因。保留未扩展的 glob 而不产生扩展错误(也许不幸的是)是标准化行为,也是传统的,因此也是预期的行为。尽管 bash 不会尝试完全符合 POSIX 规范,除非使用名称调用它sh
或传递--posix
选项,但它的许多设计选择即使不在 POSIX 模式下也直接遵循 POSIX。他们必须选择一些行为,而违背用户的期望会带来一些不利影响。
我认为这是该问题在历史方面影响最小的方面,所以我把它留到最后……但值得一提的是,行为在概念上有点奇怪nullglob
。
nullglob
乍一看似乎很优雅,因为,句法上,它处理的情况零匹配文件与一、二或任何其他数字的情况没有什么不同。我们运行的命令,其中 glob 扩展为参数,不倾向于将它们视为相同,如上所述。但从语法上讲,这至少感觉是对的,我认为这就是你提出这个问题的动机。
然而,还有另一个更微妙的矛盾nullglob
没有得到解决——它实际上放大了矛盾。零个通配符(“通配符”)的处理方式与一个、两个或任何其他数字的处理方式截然不同。例如,对于shopt -s nullglob
,如果ab?d?f
不匹配任何文件,则将其删除;如果ab?d
不匹配任何文件,则将其删除;但如果 不ab
匹配任何文件(即,如果没有名称恰好为 的文件ab
),则仍然不会将其删除。当然,如果将其删除,那将是一场灾难,因为它可能根本不打算引用当前目录中的现有文件;它甚至可能不引用文件。但这仍然消除了完全一致的希望。
bash 提供的三种行为——默认将与任何文件都不匹配的 glob 视为非 glob 并将其不扩展地传递;您期望的行为(请原谅我这种奇怪的措辞)将它们视为表示所有匹配的文件为零(nullglob
),以及安全的行为将它们视为错误(failglob
)——所有这些都代表了解决 shell 无法知道任何特定单词是否打算用作文件名所固有的歧义的不同方法。shell 执行其扩展时并不知道您用它调用的特定命令将如何处理它们的参数。
这是众多例子之一关注点分离在遵循Unix哲学设计的系统中,每个部分都旨在专做一件事并做好它。shell 将文本处理为命令和参数并调用这些命令,其中大多数命令位于 shell 本身的外部。这往往比使用外部命令的系统更好、更通用他们自己负责执行这些转换(与 DOS 和 Windows 中的传统命令处理器一样)。但它偶尔也会有缺点。
答案3
主要原因是因为这是指定的标准行为POSIX- 涵盖 shell 命令语言和其他模式匹配的标准(例如bash
,dash
shell - Ubuntu 的默认设置/bin/sh
,并ksh
遵循此标准)。来自部分2.13.3 用于文件名扩展的模式:
如果模式与任何现有文件名或路径名不匹配,则模式字符串应保持不变。
这当然有副作用 - 匹配的文件名可能确实是*.txt
。和nullglob
中的选项可以提供帮助:如果通过启用该选项(默认情况下不启用,这适用于此问题),则当找不到匹配的文件名时,globstar 将扩展为空字符串。有自己的高级模式匹配机制,可实现相同的效果bash
zsh
shopt -s nullglob
ksh93
~(N)*.txt
也可以看看为什么 nullglob 不是默认值?