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

mga*_*ort 6 bash wildcards brace-expansion files

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

打开文件

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

vim file{[1-9],1[0-9],20}.txt
Run Code Online (Sandbox Code Playgroud)

这有效。执行此命令后,Vim会打开,并在缓冲区列表里,我可以看到所有的文件file1.txtfile20.txt

创建文件

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

file[1-9].txt
file[0-9].txt
file20.txt
Run Code Online (Sandbox Code Playgroud)

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

为什么在阅读与写作时会发生这种不对称,我将来如何避免这种情况?

Adm*_*Bee 18

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

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

所以

file{[1-9],1[0-9],20}.txt
Run Code Online (Sandbox Code Playgroud)

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

file[1-9].txt file1[0-9].txt file20.txt
Run Code Online (Sandbox Code Playgroud)

然后受实际文件名扩展的影响,其中 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
Run Code Online (Sandbox Code Playgroud)

(如 pLumo 的评论中所述)


作为旁注(由@Quasimodo 建议),在bash许多其他 shell 中,通配行为可以通过shell options进行调整,bash特别是使用.shopt -s option

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

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

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

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