perl glob文件匹配用法
2014-08-10 19:08:21 阿炯

这个函数带有强烈的shell背景,它兼容了shell正则通配符,给初学者在文件匹配上带来了极大的方便,但也将shell中不好的地方也带了进来。在Shell中使用*来对文件名进行通配扩展,在Perl中也同样支持文件名通配。而且perl中的glob通配方式和shell的通配方式完全一致,实际上perl的glob函数就是直接调用csh来通配的(如果不存在csh,则使用其它shell),也因此通配是一个效率较低的操作。

perl中的glob函数的来源有两个:核心的glob(CORE::glob())、File::Glob模块中的glob函数。

看一下它们之间的异同

perldoc File::Glob

Since v5.6.0, Perl’s CORE::glob() is implemented in terms of bsd_glob(). Note that they don’t share the same prototype--CORE::glob() only accepts a single argument. Due to historical reasons, CORE::glob() will also split its argument on whitespace, treating it as multiple patterns, whereas bsd_glob() considers them as one pattern.

自5.6.0以来,Perl的核心函数glob()实现的bsd_glob()。但注意,它们不共享相同的原型:CORE::glob()只接受一个参数。由于历史原因,CORE::glob()也将空格分割其参数,将它视为多个模式,而bsd_glob()认为他们作为一个模式。

语法为:
glob EXPR

这个函数把 EXPR 的值带着 shell 那样的文件名扩展返回。它是实现 <*> 操作符的内部函数。

In list context, returns a (possibly empty) list of filename expansions on the value of EXPR such as the standard Unix shell /bin/csh would do.

In scalar context, glob iterates through such filename expansions, returning undef when the list is exhausted.

This is the internal function implementing the <*.c> operator, but you can use it directly. If EXPR is omitted, $_ is used.

glob通配函数
元字符:意义
--------------------------------
[]:字符类,匹配中括号中的任一字符。注:
*:匹配任意个字符
?:匹配任意单个字符,注意,不匹配0个
~:匹配家目录

新版本支持的匹配正则有:
\:Quote the next metacharacter
[]:Character class
{}:Multiple pattern
*:Match any string of characters
?:Match any single character
~:User name home directory

注意:
[]支持[0-9] [a-z] [A-Z] [A-z]类似的范围通配,其中[A-z]等价于[A-Za-z]
不支持[^]取反
特别需要注意的是[1-34]通配的是[1234],而不是1到34,因为中括号只能匹配单个字符


5.6 之前的版本的 Perl 使用了一个外部的处理,但 5.6 及以后的版本在内部完成。

那些第一个字符是点(“.”)的文件被忽略,除非这个字符是明确匹配的。一个星号(“*”)匹配任意字符的任意序列(包括空)。一个问号(“?”)匹配任意一个字符。一个方括弧序列(“[ ... ]”)声明一个简单的字符表,比如“[chy0-9]”。字符表可以用^取反,如“*.[^oa]”,它匹配任意非点文件,后面跟着一个字符在文件名尾部,但这个字符既不能是“a”也不能是“o”。一个波浪号(“~”)扩展成一个家目录,如“~/.*rc”是指当前用户的所有 “rc”文件,或者“~freeoa/Mail/*”似乎所有 freeoa 的邮件文件。花括弧可以用于候补,如在“!/.{mail,ex,csh,twm,}rc”里面的是获取那些特定的 rc 文件。

如果想找到那些可能包含空白的文件名,可能需要直接使用 File::Glob 模块,因为老版本的 glob 把空白用于分隔多个模式,比如 <*.c *.h>。调用 glob (或者 <*> 操作符)自动 use 该模块。

此类操作有两种实现方式:<>操作符和glob函数

my @spacies = glob("*e f*");
将返回以'e'结尾,'f'开头的文件名集合数组。

my @spacies = <*{c,h,e}>;
将返回以'c','h','e'结尾的文件名,当然也含目录,下同。

my @spacies = <*[che]>;

下面这个例子打算匹配以'freeoa'开头,'-'两边有空格,以'pl'为扩展名的文件。
my @spacies =glob("/path/to/dir/freeoa - *.pl");

但实际上返回的是:'freeoa','-',所有的以'pl'为扩展名的文件。
$VAR1 = [
 '/path/to/dir/freeoa',
 '-',
 'glb.pl',
 'jabber.pl',
 'sendeim.pl'
];

这里将介绍处理文件名中包含空格的情况。像上述的如果是在调用unlink操作时就比较危险了,它没有按我们的预期返回。

Note that glob splits its arguments on whitespace and treats each segment as separate pattern. As such, glob("*.c *.h") matches all files with a .c or .h extension. The expression glob(".* *") matches all files in the current working directory. If you want to glob filenames that might contain whitespace, you'll have to use extra quotes around the spacey filename to protect it. For example, to glob filenames that have an e followed by a space followed by an f , use either of:

注意:对于传入的一系列参数以空白将其每一段作为单独的模式。因此,glob(“*.c *.h”)将匹配所有文件.c或.h扩展。表达式glob(”.* *”)匹配当前工作目录中的所有文件。如果你想匹配一些可能包含空格的文件名,就需要使用额外的引号(")来包括文件名来保护它。例如,glob(f e)这种紧随其后的是空格的话,使用:

@spacies = <"*e f*">;
@spacies = glob '"*e f*"';
@spacies = glob q("*e f*");

变量前缀替换
If you had to get a variable through, you could do this:
@spacies = glob "'*${var}e f*'";
@spacies = glob qq("*${var}e f*");

另外可用glob函数来生成一系列的组合数组:
my @spacies = glob "{a,b,c,d}={1,2,3}";

将会得到一个包含4*3=12个元素的数组。


应用举例

@htmlfiles=glob '/home/freeoa/*.html';
@tles=glob '*.???';
@lcfirst=glob '[a-z]*';
@files=glob 'file[0-9][0-9][0-9]';


use 5.10;
say glob "*"; # 匹配当前目录下所有非"."开头的隐藏文件
say glob ".* *"; # 匹配当前目录下所有文件,包括"."开头的隐藏文件
say glob "23.p[ly]"; # 匹配23.pl或23.py文件
say glob "[0-9][0-9].pl"; # 匹配两个数值开头的pl文件
say glob "/root/23.p?"; # 匹配家目录下后缀以p开头,后面还有一个字符的文件
say glob "~/*.sh"; # 匹配家目录下的所有sh文件

在glob函数中,空格具有特殊含义,如果想要匹配包含空格的文件名,必须将其使用引号(单/双引号皆可)包围。要匹配"hello world.log"文件:
glob "'hello w*.log'";
glob '"hello w*.log"';
glob qq('hello w*.log');
glob qq("hello w*.log");
glob q('hello w*.log');
glob q("hello w*.log");

File::Glob模块提供更丰富的通配规则,可以去查看下手册。不过说实话,用到的几乎应该不多。

尖括号<>通配写法

在glob出现之前,人们都使用尖括号表达式来通配。它和glob的实现是完全一致的,仅仅只是从尖括号改为了glob函数。例如,匹配/root下的sh文件,下面两种写法完全等价:
say </root/*.sh>;
say glob "/root/*.sh";

还可以使用变量替换:
$dir="/etc";
my @file_list = glob "$dir/*.sh $dir/*.pl";
my @file_list = <$dir/*.sh $dir/*.pl>;

同样,匹配包含空格的文件,可能需要使用引号包围:
say <"hello w*.log">;

在这里需要搞清楚尖括号内的到底会被解析成文件句柄还是解析成通配符。perl的解析规则是:假如尖括号内的内容满足标识符规则(文件句柄的名称要满足此规则),则会解析为文件句柄,否则解析成通配符。

以下是几种情况的区分示例:
my @files = <FOO/*>; # 文件名通配
my @lines = <FOO>; # 读取文件句柄
my @lines = <$fred>; # 读取文件句柄
my $name = 'FOO';
my @files = <$name>; # 读取文件句柄
my @files = <$name/*>; # 文件名通配

从第三行和第5行可以看出,当使用变量且只有变量名替换的时候,会优先解析为文件句柄。

文件查找之find2perl脚本

在unix下有一个find指令,用来查找文件非常方便。perl提供了一个find2perl的工具(该工具是在安装perl时自带的),它可以将find查找文件时的表达式转换成perl对应的查找语句。其选项和用法和find的用法基本一样,只有几项额外的是find2perl自身提供的,但这样的选项非常少。

注意,find2perl不是文件查找工具,而是将我们写的find命令表达式转换为等价的perl文件查找语句。例如搜索/etc目录下所有".cnf"结尾的文件,find命令的表达式如下:
# find /etc -type f -name "*.cnf"

执行find2perl:
# find2perl /etc/ -type f -name "*.cnf"
#!/usr/bin/perl -w
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if 0; #$running_under_some_shell

use strict;
use File::Find ();
# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.
# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

sub wanted;

# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, '/etc/');
exit;

sub wanted{
    my ($dev,$ino,$mode,$nlink,$uid,$gid);
    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
    -f _ &&
    /^.*\.cnf\z/s
    && print("$name\n");
}

可以看出,上面生成了一个wanted子程序,以后要查找"/etc/*.cnf"文件时,只需调用wanted子程序即可。例如:
# find2perl /etc/ -type f -name "*.cnf" > freeoa.plx
# chmod +x freeoa.plx
# perl freeoa.plx
/etc/my.cnf
/etc/proxysql.cnf
/etc/pki/tls/openssl.cnf


参考来源:
glob