flume-taildir/glob语法/路径迭代
最近在使用flume的taildir做多文件监控,发现配置路径的增则表达式不起作用,跟踪源码发现:
TaildirMatcher(String fileGroup, String filePattern, boolean cachePatternMatching) {
// store whatever came from configuration
this.fileGroup = fileGroup;
this.filePattern = filePattern;
this.cachePatternMatching = cachePatternMatching;
// calculate final members
File f = new File(filePattern);
this.parentDir = f.getParentFile();
String regex = f.getName();
final PathMatcher matcher = FS.getPathMatcher("regex:" + regex);
this.fileFilter = new DirectoryStream.Filter<path>() {
@Override
public boolean accept(Path entry) throws IOException {
return matcher.matches(entry.getFileName()) && !Files.isDirectory(entry);
}
};
// sanity check
Preconditions.checkState(parentDir.exists(),
"Directory does not exist: " + parentDir.getAbsolutePath());
}
其中的
final PathMatcher matcher = FS.getPathMatcher("regex:" + regex);
这一行是用来做正则匹配的。
这里的参数有两种类型:一是glob;另一是regex的。flume默认使用的是regex类型,其是java.util.regex.Pattern
的java类型的正则表达式。这两种类型的正则匹配不一样,所以导致配置路径不起作用。至于glob则是下面介绍的一种
Glob
Glob
是一种模式匹配,类似于正则表达式但是语法相对简单。Glob
语句是一个含有 *,?{}[]
这些特殊字符的字符串,并与目标字符串匹配。可以用来做文件路径匹配或文件查找。例如 Linux
下的命令 ls *.txt
,其中的 *.txt
就是一个 glob
语句。
语法规则
Java
语言中的 Glob
语法遵循几个简单的规则:
- *
匹配任意个数的字符,包括空。不包括路径边界 /
或 \
。例如 /path/*/abc
可以匹配 /path/a/abc
和 /path/b/abc
等。
- **
和一个星号类似,区别是可以跨路径边界,一般用来匹配多级目录。例如 /path/**/abc
可以用来匹配 /path/a/abc
、/path/b/abc
、/path/a/b/abc
、/path/a/b/c/abc
等。
- 一个问号 ?
匹配任意一个字符
- {}
大括号用来指定一个子模式匹配集合,例如:{sun,moon,stars}
可以匹配 sun
moon
starts
, {temp*, tmp*}
可以匹配任何以 temp
tmp
开头的字符串等。
- []
方括号表示匹配括号内的任意一个单个字符,当有 -
时表示匹配任意一个连续范围内的单个字符。例如:[aeiou]
匹配任意一个小写元音字符,[0-9]
匹配任意一个数字,[A-Z]
匹配任意一个大写字母,[a-z,A-Z]
匹配任意一个大写或小写字母。另外,在方括号内, *
?
\
字符仅匹配它们自身。
- 任意其它字符
任意的其他字符表示匹配它们自身。
- 反斜杠\转义
匹配 *
?
或其他特殊字符需要使用反斜杠\
转义。例如: \\
匹配一个反斜杠,\?
匹配一个问号。
举例说明
Glob | 说明 |
---|---|
*.log | 匹配所有.log结尾的字符串 |
?? | 匹配所有有两个数字或字母构成的字符串,aa,a1 |
*[0-9]* | 匹配所有含有一个数字的字符串 |
*.{htm,html,pdf} | 匹配所有以 .htm .html 或 .pdf 结尾的字符串 |
a?*.java | 匹配所有以 a 开头,且a 之后至少由一个字母或数字,且以 .java 结尾的字符传 |
{foo*,[0-9]} | 匹配所有以 foo 开头,或含有数字的字符串,如 foobar x1y foo1xyz |
代码
JDK
中 glob
相关的内容主要在 java.nio.file
包内,其中 glob
语法转正则表达式的实现在 sun.nio.fs.Globs.java
中。下面代码是一个寻找/lihao/test/
目录下所有 jpg
文件的例子:
String match = "glob:/lihao/test/**/*.jpg";
final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(match);
SimpleFileVisitor<path> fileVisitor = new SimpleFileVisitor<path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
if (pathMatcher.matches(file)) {
System.out.println("find: " + file.toFile().toString())
}
return super.visitFile(file, attrs);
}
};
try {
Files.walkFileTree(Paths.get(dir.getPath()), fileVisitor);
} catch (IOException e) {
e.printStackTrace();
}
参考
taildir监控文件迭代问题源码修改
在TaildirMatcher
类中使用如下代码覆盖相应的代码即可将完成路径的迭代问题
private List<file> getMatchingFilesNoCache() {
List<file> result = Lists.newArrayList();
List<path> paths = recurseFolder(parentDir);
for(Path path:paths){
try (DirectoryStream<path> stream = Files.newDirectoryStream(path, fileFilter)) {
for (Path entry : stream) {
result.add(entry.toFile());
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
public List<path> recurseFolder(File root) {
List<path> allParentFolders = new ArrayList<>();
if (root.exists()) {
allParentFolders.add(root.toPath());
File[] files = root.listFiles();
if (null == files || files.length == 0) {
return allParentFolders;
} else {
for (File subFile : files) {
if (subFile.isDirectory()) {
allParentFolders.addAll(recurseFolder(subFile));
}
}
}
}
return allParentFolders;
}