打开和创建文件时 Bash 扩展不对称

打开和创建文件时 Bash 扩展不对称

我在 Bash 正则表达式中遇到了以下不对称示例,这让我感到困惑。我想知道我正在做的事情是非标准的并导致这种行为,或者我所缺少的这种行为背后的逻辑是什么。

打开文件

假设我有一个目录,其中包含file1.txt名为file20.txt.我想在我最喜欢的文本编辑器中打开它们。为此,从某种意义上说,Bash 必须“读取”目录的内容并将它们传递给 Vim。我可以使用以下正则表达式来实现此目的:

vim file{[1-9],1[0-9],20}.txt

这有效。执行此命令后,Vim 打开,在缓冲区列表中我可以file1.txt看到file20.txt.

创建文件

现在假设我们处于不同的场景:我们从一个空目录开始,并且想要将文件创建file1.txtfile20.txt.为此,从某种意义上说,Bash 必须将文件名“写入”目录。不幸的是,在这种情况下,前面的命令不起作用。我最终没有创建所需的 20 个文件,而是在缓冲区列表中得到了以下文件:

file[1-9].txt
file[0-9].txt
file20.txt

[]因此,它们已合并到名称中,而不是将方括号解释为正则表达式的一部分。

为什么在阅读和写作时会出现这种不对称?将来如何避免这种情况?

答案1

您使用的不是正则表达式,而是组合大括号扩展文件名扩展(又名通配符)。这很重要,因为虽然大括号扩展只是将包含构造的字符串扩展{ ... }为几个不同的字符串,但通配部分实际上试图将现有文件与模式匹配。这就是问题所在(顺便说一句,甚至正则表达式也用于将现有字符串与模式匹配,不根据模式生成字符串)。

特别要注意的是,大括号扩展是在文件名扩展之前执行的。

所以

file{[1-9],1[0-9],20}.txt

由 shell 扩展为三个空格分隔的标记

file[1-9].txt file1[0-9].txt file20.txt

然后,它们会受到实际的文件名扩展的影响,其中 shell 检查哪些现有文件的 与该 glob 模式匹配。重要的是,如果没有文件与其中一种模式匹配,则模式按字面意思理解

所以在你打开的情况下,会发生什么

  1. vim file{[1-9],1[0-9],20}.txt被扩展为vim file[1-9].txt file1[0-9].txt file20.txt
  2. vim file[1-9].txt file1[0-9].txt file20.txt被扩展为vim file1.txt file2.txt ... file20.txt因为所有这些文件都存在(它会不是扩展到该数字范围内的任何不存在的文件)
  3. vim打开所有这些文件。

但是,当使用touch具有相同参数的eg时创建不存在的文件,发生的情况是

  1. touch file{[1-9],1[0-9],20}.txt被扩展为touch file[1-9].txt file1[0-9].txt file20.txt
  2. 由于没有文件与该模式匹配,因此[1-9],1[0-9]20仍然存在字面上地
  3. touch创建这三个文件,其名称按字面意思命名。

如果您想避免这种情况,并且由于您想创建该范围内的所有文件,您可以简单地将命令行限制为大括号扩展,即

touch file{1..20}.txt

(pLumo 的评论中也指出)


作为旁注(由@Quasimodo建议),在bash和许多其他外壳中,可以通过以下方式调整通配行为外壳选项,在bash具体使用时。shopt -s option

在这里,该选项特别nullglob有趣,因为它将使 shell 扩展一个不将任何文件名与空字符串匹配的通配模式,而不是将模式字面保留在其中。如果您想使用循环迭代与模式匹配的所有文件,这特别有用for

  • 没有选项nullglob,形式的循环
    for f in *.txt
    
    将执行一次并$f设置为文字 *.txt如果.txt当前目录中不存在,这可能会导致意外行为(即代码尝试对不存在的文件进行操作)
  • nullglob选项,shell 根本不会进入循环体。

另一方面(正如 @Barmar 所正确指出的),stdin如果您为文件提供一个计算结果为“无”的 glob 模式,许多对文件进行操作的程序将默默地尝试读取,因为没有文件名匹配,因此使用此选项可以有如果你不小心的话,会出现奇怪的副作用。

此外nullglob,Bash 有一个failglob选项,如果存在不匹配任何内容的 glob,该选项将给出错误而不是运行命令。

相关内容