通配模式

Perl中使用glob函数来通配文件,支持的通配语法如下:

\       转义下一个通配元字符
[]      字符类,可通配中括号中的单个字符,可使用范围模式
{}      多模式,例如`a{a,b}c`可通配aac和abc
*       匹配任意长度的字符(无法通配文件名开头的点)
?       匹配任意单个字符(无法通配文件名开头的点)
~       家目录

例如,glob("*.sh")将匹配当前目录下的所有sh脚本文件。

glob函数在列表上下文中返回所有通配成功的文件列表,在标量上下文则迭代每个被通配成功的文件名,迭代完成后返回undef值。

# 当前目录下所有文件(不会递归子目录)
while(glob("*")){
  say $_;
}

注意,glob内部使用的是Csh通配规则,因此会识别通配表达式中的空格,并根据空格划分token。例如,glob("*.c .*.c")是两个通配表达式,表示通配c文件和以点开头的c文件,它们是逻辑或的关系。glob("* .*")表示通配当前目录所有文件(包括...)。

如果想明确表示通配表达式中的空格是通配表达式中的一部分,而不是将其作为分隔符,需要将通配表达式整体进行引用包围。例如glob('"*e f*"')或者glob(q("*e f*"))

因此,当通配符中使用了变量时,最好将它使用引号包围起来,避免变量值中包含空格。

glob通配时不会递归到子目录中去通配,如果也想要搜索子目录中的文件,可考虑使用find而不是通配。

通配操作也并非总是用来通配,它的{}通配语法也可以用来生成字符串:

$ perl -E '@arr = glob("a{b,c}d{e,f}h");say "@arr"'
abdeh abdfh acdeh acdfh

<>中的通配语法

<>除了可以用来读取文件句柄,还可以用来进行通配操作。

当perl发现<>中的不是一个标量变量(如$fh)或不是一个裸句柄变量(如STDIN/ARGV),就会将<>中的东西当作通配符进行文件通配。

while(<"*">){  # 通配当前目录的所有非隐藏文件
  say "$_";
}

注意,<>中的通配符不像glob()一样会因为空格而划分通配表达式,<>中的通配表达式在转换为内部的glob通配表达式时,默认会在外层加上一层引号。因此,<>中的通配表达式总是单个表达式整体。

# 错误的写法
# 含义是匹配文件名中带有空格和点的文件名
while(<"* .*">){}

File::Glob

默认情况下,glob函数使用的是Csh的通配规则,<>方式的通配内部使用的也是glob,因此也是csh通配。

但有时候,使用csh通配功能会比较受限,如果想要使用功能更丰富的通配,可以使用File::Glob模块。File::Glob模块提供了csh_globbsd_glob,csh_glob即核心模块中原始的glob函数,bsd_glob功能则更丰富,它支持为通配规则指定修饰符,比如不区分大小写、家目录扩展等修饰符。

需注意的是,<>方式的通配是根据当前的通配规则来生成文件路径的,因此导入File::Glob的功能时,可能会对<>产生影响,例如导入:nocase属性,将使得<>中的通配不区分大小写,导入:glob将覆盖原始的glob函数,使得<>中的通配采用File::Glob:glob规则。

使用File::Glob方式很简单:

#!/usr/bin/env perl

use 5.010;
use File::Glob qw(:bsd_glob :csh_glob :globally :nocase);
# 导入:nocase后,通配将不区分大小写

# `<>`方式的通配将不区分大小写
while(<*.LOG>){ say $_; }
# 明确表示使用globally通配,它等价于原生的glob函数
for(globally("*.LOG")){ say $_; }
# 明确表示使用bsd_glob通配
for(bsd_glob("*.LOG")){ say $_; }
# 明确表示使用csh_glob通配
for(csh_glob("*.LOG")){ say $_; }

csh_glob和原始的glob一样,会将通配表达式中的空格识别为分隔符。bsd_glob则不会分隔为多个通配表达式,而是将通配表达式看作是整体。因此,如果想要在bsd_glob中指定多个通配模式,只能使用{}语法,例如bsd_glob("{a*,b*}")等价于csh_glob("a* b*")

bsd_glob允许指定两个参数,一个是必选的通配表达式,第二个参数是可选的通配修饰符:

my $homedir = bsd_glob('~jrhacker', GLOB_TILDE | GLOB_ERR);
if (GLOB_ERROR) {
    # An error occurred expanding the home directory.
}