fnmatch和glob模式匹配

有时候需要一些简单的模式匹配,正则有点杀鸡用牛刀的意思,可以考虑The GNU C Library提供的fnmatch函数:

#include 
int fnmatch(const char *pattern, const char *string, int flags);

fnmatch用来检查string是否匹配pattern所表示的模式,其使用shell通配符模式,具体规则参见glob(7),主要有:

?(不在中括号内的):匹配单个字符
*(不在中括号内的):匹配一个字符串,包括空字符串
对于一个路径,匹配分别应用于其被/分隔的各个部分,/不会被?*匹配到
.开头的文件必须显式匹配,例如rm *并不会删除.profile
[...](第一个字符不是!):匹配被中括号包围的任一字符,括号间不可为空,例如[][!]匹配][!
中括号内可以使用range,例如[1-5]匹配1,2,3,4,5,如果要匹配-,要把-放到括号中的最前或最后位置。[--0]匹配-.0/不会被匹配,因为它是目录分隔符
如果中括号内首字符是!:表示取反,其中字符不会被匹配到

关于参数flag
FNM_NOESCAPE 将反斜杠视为普通字符
FNM_PATHNAME(或FNM_FILE_NAME) *?[]中的/不匹配字符串中的/,只能显式指定/,比如:

fnmatch("*", "a/b", FNM_PATHNAME) 不匹配
fnmatch("*", "a/b", 0) 匹配

FNM_PERIOD 模式不能匹配.开头的字符串,除非显式指定,如果同时设置了FNM_PATHNAME,上述特殊处理还适用于字符串中/之后.开头的子串,比如:

fnmatch("*", ".b", FNM_PERIOD) 不匹配
fnmatch(".*", ".b", FNM_PERIOD) 匹配
fnmatch("*/*", "a/.b", FNM_PATHNAME|FNM_PERIOD) 不匹配

FNM_CASEFOLD 忽略大小写

关于返回值
如果匹配成功返回0,如果没有匹配返回FNM_NOMATCH,如果有错误返回其他非零值

Extended Globbing
ksh引入了extended patterns,现在也被其他shell支持。默认情况下extglob是关闭的,需要用shopt -s extglob开启1
格式如下(可参考fnmatch(3)):

?(pattern-list) – Matches zero or one occurrence of the given patterns
*(pattern-list) – Matches zero or more occurrences of the given patterns
+(pattern-list) – Matches one or more occurrences of the given patterns
@(pattern-list) – Matches exactly one occurrence of the given patterns
!(pattern-list) – Matches anything except the given patterns

除了@,the leading character和正则表达式中的含义类似2

Bash              Regular Expression
?(pattern-list)   (...)?
*(pattern-list)   (...)*
+(pattern-list)   (...)+
@(pattern-list)   not a RE syntax
!(pattern-list)   "!" used as for negative assertions in RE syntax

举例来说,
+(2).jpg会匹配2.jpg,22.jpg,222.jpg……
+(ab).jpg会匹配ab.jpg,abab.jpg,ababab.jpg……

pattern-list可以由多个pattern组成,每个pattern用|隔开,例如:
@(ab|def)*@(.jpg|.gif)匹配已ab或def开头的jpg或gif图片
1122199m匹配模式+(1??|?1?2)m,但不匹配@(1??|?1?2)m,因为1122匹配上了?1?2,199匹配上了1??,共匹配上了两次。

多字节字符串和宽字符串
fnmatch只能处理单字节字符串,如果源文件是utf8编码的,???才匹配一个中文,如果源文件是gbk编码的,??匹配一个中文。但我们在使用shell时,?却能匹配一个中文字符。
当shell在命令的参数中遇到了通配符时,会将其当做路径或文件名区在磁盘上搜寻最可能的匹配,若符合要求的匹配存在,则进行替换,否则就将该通配符作为一个普通字符传递给命令,然后由命令进行处理。3
所以模式匹配是由shell实现的,我找了 bash 的源码,发现其有一个glob的库来实现匹配功能,它有两个函数实现了与fnmatch近似的功能,并且参数和返回值含义也相同:

int strmatch(const char *pattern, const char *string, int flags);
int wcsmatch(const wchar_t *wpattern, const wchar_t *wstring, int flags);

strmatch支持多字节字符串,wcsmatch用于宽字符串。strmatch内部是先将多字节字符串转换为宽字符串,再做的匹配。窄字符串和宽字符的匹配方法则是相同的。

处理多字节字符串和宽字符串时,如果源文件使用utf8编码,需要用setlocale将LC_CTYPE也设置为utf8,比如setlocale(LC_CTYPE, "en_US.utf8")

其他模式匹配方法
glob用于寻找当前目录下符合模式的pathnames。
int glob(const char *pattern, int flags, int (*errfunc) (const char *epath, int eerrno), glob_t *pglob);
输出当前目录下所有txt文件:

glob_t gbuf;
gbuf.gl_offs = 0;
glob("*.txt", GLOB_DOOFFS, NULL, &gbuf);
for(size_t i = 1; i <= gbuf.gl_pathc; ++i)
{
	printf("%d: %s\n", i, gbuf.gl_pathv[i-1]);
}
globfree(&gbuf);

glob函数中No tilde expansion or parameter substitution is done; if you want these, use wordexp。
int wordexp(const char *s, wordexp_t *p, int flags);
同样这两个函数也只支持单字节字符串。

fnmatch、glob、wordexp都是GNU C Library第10章模式匹配中的一部分,该章还包括正则表达式匹配,详细参见 Pattern Matching。


  1. Extended globbing ↩︎

  2. Bash Extended Globbing ↩︎

  3. Linux Bash之通配符 ↩︎

你可能感兴趣的:(Linux编程,c语言,开发语言)