我在 Bash 正则表达式中遇到了以下不对称示例,这让我感到困惑。我想知道我正在做的事情是非标准的并导致这种行为,或者我所缺少的这种行为背后的逻辑是什么。
打开文件
假设我有一个目录,其中包含file1.txt
名为file20.txt
.我想在我最喜欢的文本编辑器中打开它们。为此,从某种意义上说,Bash 必须“读取”目录的内容并将它们传递给 Vim。我可以使用以下正则表达式来实现此目的:
vim file{[1-9],1[0-9],20}.txt
这有效。执行此命令后,Vim 打开,在缓冲区列表中我可以file1.txt
看到file20.txt
.
创建文件
现在假设我们处于不同的场景:我们从一个空目录开始,并且想要将文件创建file1.txt
到file20.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 模式匹配。重要的是,如果没有文件与其中一种模式匹配,则模式按字面意思理解。
所以在你打开的情况下,会发生什么
vim file{[1-9],1[0-9],20}.txt
被扩展为vim file[1-9].txt file1[0-9].txt file20.txt
vim file[1-9].txt file1[0-9].txt file20.txt
被扩展为vim file1.txt file2.txt ... file20.txt
因为所有这些文件都存在(它会不是扩展到该数字范围内的任何不存在的文件)vim
打开所有这些文件。
但是,当使用touch
具有相同参数的eg时创建不存在的文件,发生的情况是
touch file{[1-9],1[0-9],20}.txt
被扩展为touch file[1-9].txt file1[0-9].txt file20.txt
- 由于没有文件与该模式匹配,因此
[1-9]
,1[0-9]
和20
仍然存在字面上地 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,该选项将给出错误而不是运行命令。