C 语言通配符

2015-09-08 language linux c/cpp

简单介绍下 C 语言中与字符串操作相关的函数。

简介

当 shell 在参数中遇到了通配符时,会尝试将其当作路径或文件名去在磁盘上搜寻可能的匹配:若符合要求的匹配存在,则进行代换(路径扩展);否则就将该通配符作为一个普通字符传递给命令,然后再由命令进行处理。

也就是说,通配符是由 shell 处理的,在传递给可执行程序之前已经进行了处理,实际上就是一种 shell 实现的路径扩展功能。

*                       匹配0或多个字符             a*b  ab a012b aabcd
?                       匹配任意一个字符            a?b  abb acb a0b
[list]                  匹配list中的任意单一字符    a[xyz]b  axb ayb azb
[!list]                 匹配除list中的任意单一字符  a[!0-9]b  axb abb a-b
[c1-c2]                 匹配c1到c2中的任意单一字符  a[0-9]b   a0b a9b
{string1,string2,...}   匹配sring1或string2(或更多)其一字符串

另外,需要注意 shell 通配符和正则表达式之间的区别。

fnmatch()

就是判断字符串是不是符合 pattern 所指的结构,这里的 pattern 是 shell wildcard pattern,其中部分匹配行为可以通过 flags 配置,详见 man 3 fnmatch

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <fnmatch.h>
#include <sys/types.h>

int main(void)
{
	int rc;
	DIR *dir;
	struct dirent *entry;
	const char *pattern = "*.log";

	dir = opendir("/tmp");
	if (dir == NULL) {
		perror("opendir()");
		exit(EXIT_FAILURE);
	}
	while ((entry = readdir(dir)) != NULL) { // 逐个获取文件夹中文件
		rc = fnmatch(pattern, entry->d_name, FNM_PATHNAME | FNM_PERIOD);
		if (rc == 0) {         // 符合pattern的结构
			printf("match %s\n", entry->d_name);
		} else if (rc == FNM_NOMATCH){
			continue ;
		} else {
			printf("error %d file=%s\n", rc, entry->d_name);
		}
	}
	closedir(dir);
	return 0;
}

wordexp()

按照 Shell-Style Word Expansion 扩展将输入字符串拆分,返回的格式为 wordexp_t 变量,其中包括了三个变量,两个比较重要的是:A) we_wordc 成员数;B) we_wodv 数组。

注意,在解析时会动态分配内存,所以在执行完 wordexp() 后,需要执行 wordfree();另外,如果遇到内存不足会返回 WRDE_NOSPACE 错误,此时可能已经分配了部分地址,所以仍需要执行 wordfree()

  1. 按照空格解析;2) 正则表达式;3) 环境变量。
#include <stdio.h>
#include <stdlib.h>
#include <wordexp.h>

int main(void)
{
	int i, ret;
	wordexp_t p;

	ret = wordexp("foo bar $SHELL *[0-9].c *.c", &p, 0);
	if (ret == WRDE_NOSPACE) {
		wordfree(&p);
		exit(EXIT_FAILURE);
	} else if (ret != 0) {
		exit(EXIT_FAILURE);
	}
	for (i = 0; i < p.we_wordc; i++)
		printf("%s\n", p.we_wordv[i]);
	wordfree(&p);

	return 0;
}

glob

对于通配符的匹配,还可以使用 glob() 函数。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <glob.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(void)
{
    glob_t globbuf;
    struct stat statbuf;
    int rc;
    size_t pathc;
    char **pathv;

    rc = glob("/var/run/haproxy[0-9]*.sock", GLOB_ERR | GLOB_NOSORT, NULL, &globbuf);
    if (rc != 0) {
        if (rc == GLOB_NOMATCH)
            return 0;
        else
            return -1;
    }

    pathv = globbuf.gl_pathv;
    pathc = globbuf.gl_pathc;
    printf("Got #%zd matches\n", pathc);

    for (; pathc-- > 0; pathv++) {
        rc = lstat(*pathv, &statbuf);
        if (rc < 0 || !S_ISSOCK(statbuf.st_mode))
            continue;
        printf("Match path: %s\n", *pathv);
    }

    globfree(&globbuf);
    return 0;
}

qsort()

只用于数组的排序,对于链表等无效。

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
    base  : 数组的基地址
    nitems: 数组包含的元素;
    size  : 每个元素的大小;
    compar: 比较函数;

示例程序如下。

#include <stdio.h>
#include <stdlib.h>

int cmpfunc(const void *a, const void *b)
{
	return (*(int*)a - *(int*)b);
}

int main(void)
{
	int n;
	int values[] = { 88, 56, 100, 2, 25 };

	printf("Before sorting the list is: \n");
	for (n = 0 ; n < 5; n++)
		printf("%d ", values[n]);
	putchar('\n');
	qsort(values, 5, sizeof(int), cmpfunc);
	printf("After sorting the list is: \n");
	for( n = 0 ; n < 5; n++ )
		printf("%d ", values[n]);
	putchar('\n');

	return(0);
}

最佳实践

如果只想匹配一些通配符文件,那么就可以将 flag 设置为 WRDE_NOCMD | WRDE_UNDEF 以防止一些非法的入参。通过 wordexp() 过滤通配符文件,然后可以通过 qsort() 进行排序,示例如下。

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <wordexp.h>
#include <sys/stat.h>

static int config_cmp_string(const void * a, const void * b)
{
    return strcmp(*(char **)a, *(char **)b);
}

int main(void)
{
	int rc, i;
	wordexp_t we;
	struct stat statinfo;
	const char *pattern = "/tmp/*.log", *path;

	rc = wordexp(pattern, &we, WRDE_NOCMD | WRDE_UNDEF);
	if (rc != 0) {
		fprintf(stderr, "wordexp(%s) failed, rc %d.\n", pattern, rc);
		exit(EXIT_FAILURE);
	}
	qsort((void *)we.we_wordv, we.we_wordc, sizeof(*we.we_wordv), config_cmp_string);

	for (i = 0; i < (int)we.we_wordc; i++) {
		path = we.we_wordv[i];
		if (stat(path, &statinfo) != 0) {
			fprintf(stderr, "stat(%s) failed, %s.\n", path, strerror(errno));
			continue;
		}

		if (S_ISREG(statinfo.st_mode)) {
			fprintf(stdout, "regular file: %s\n", path);
		} else if (S_ISDIR(statinfo.st_mode)) {
			fprintf(stdout, "directory: %s\n", path);
		}
	}
	wordfree(&we);

	return 0;
}