是否有一个相当于“glob”types模式的java.util.regex?
是否有一个标准(最好是Apache Commons或类似的非病毒)库在Java中进行“glob”types匹配? 当我不得不在Perl中做类似的时候,我只是把所有的“ .
”改为“ \.
”,把“ *
”改成“ .*
”,把“ ?
”改成“ .
”等等,但是我想知道是否有人为我完成了这项工作。
类似的问题: 从globexpression式创build正则expression式
没有任何内置的东西,但是将类似于glob的东西转换成正则expression式非常简单:
public static String createRegexFromGlob(String glob) { String out = "^"; for(int i = 0; i < glob.length(); ++i) { final char c = glob.charAt(i); switch(c) { case '*': out += ".*"; break; case '?': out += '.'; break; case '.': out += "\\."; break; case '\\': out += "\\\\"; break; default: out += c; } } out += '$'; return out; }
这适用于我,但我不知道它是否覆盖了glob“标准”,如果有一个:)
Paul Tomblin更新:我发现一个perl程序可以进行全局转换,并将其转换为Java,最终:
private String convertGlobToRegEx(String line) { LOG.info("got line [" + line + "]"); line = line.trim(); int strLen = line.length(); StringBuilder sb = new StringBuilder(strLen); // Remove beginning and ending * globs because they're useless if (line.startsWith("*")) { line = line.substring(1); strLen--; } if (line.endsWith("*")) { line = line.substring(0, strLen-1); strLen--; } boolean escaping = false; int inCurlies = 0; for (char currentChar : line.toCharArray()) { switch (currentChar) { case '*': if (escaping) sb.append("\\*"); else sb.append(".*"); escaping = false; break; case '?': if (escaping) sb.append("\\?"); else sb.append('.'); escaping = false; break; case '.': case '(': case ')': case '+': case '|': case '^': case '$': case '@': case '%': sb.append('\\'); sb.append(currentChar); escaping = false; break; case '\\': if (escaping) { sb.append("\\\\"); escaping = false; } else escaping = true; break; case '{': if (escaping) { sb.append("\\{"); } else { sb.append('('); inCurlies++; } escaping = false; break; case '}': if (inCurlies > 0 && !escaping) { sb.append(')'); inCurlies--; } else if (escaping) sb.append("\\}"); else sb.append("}"); escaping = false; break; case ',': if (inCurlies > 0 && !escaping) { sb.append('|'); } else if (escaping) sb.append("\\,"); else sb.append(","); break; default: escaping = false; sb.append(currentChar); } } return sb.toString(); }
我正在编辑这个答案,而不是自己做,因为这个答案使我走上正轨。
Java 7中也计划实施全球化。
请参阅FileSystem.getPathMatcher(String)
和“查找文件”教程 。
感谢大家的贡献。 我写了比以前任何答案都更全面的转换:
/** * Converts a standard POSIX Shell globbing pattern into a regular expression * pattern. The result can be used with the standard {@link java.util.regex} API to * recognize strings which match the glob pattern. * <p/> * See also, the POSIX Shell language: * http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_01 * * @param pattern A glob pattern. * @return A regex pattern to recognize the given glob pattern. */ public static final String convertGlobToRegex(String pattern) { StringBuilder sb = new StringBuilder(pattern.length()); int inGroup = 0; int inClass = 0; int firstIndexInClass = -1; char[] arr = pattern.toCharArray(); for (int i = 0; i < arr.length; i++) { char ch = arr[i]; switch (ch) { case '\\': if (++i >= arr.length) { sb.append('\\'); } else { char next = arr[i]; switch (next) { case ',': // escape not needed break; case 'Q': case 'E': // extra escape needed sb.append('\\'); default: sb.append('\\'); } sb.append(next); } break; case '*': if (inClass == 0) sb.append(".*"); else sb.append('*'); break; case '?': if (inClass == 0) sb.append('.'); else sb.append('?'); break; case '[': inClass++; firstIndexInClass = i+1; sb.append('['); break; case ']': inClass--; sb.append(']'); break; case '.': case '(': case ')': case '+': case '|': case '^': case '$': case '@': case '%': if (inClass == 0 || (firstIndexInClass == i && ch == '^')) sb.append('\\'); sb.append(ch); break; case '!': if (firstIndexInClass == i) sb.append('^'); else sb.append('!'); break; case '{': inGroup++; sb.append('('); break; case '}': inGroup--; sb.append(')'); break; case ',': if (inGroup > 0) sb.append('|'); else sb.append(','); break; default: sb.append(ch); } } return sb.toString(); }
而unit testingcertificate它的工作原理:
/** * @author Neil Traft */ public class StringUtils_ConvertGlobToRegex_Test { @Test public void star_becomes_dot_star() throws Exception { assertEquals("gl.*b", StringUtils.convertGlobToRegex("gl*b")); } @Test public void escaped_star_is_unchanged() throws Exception { assertEquals("gl\\*b", StringUtils.convertGlobToRegex("gl\\*b")); } @Test public void question_mark_becomes_dot() throws Exception { assertEquals("gl.b", StringUtils.convertGlobToRegex("gl?b")); } @Test public void escaped_question_mark_is_unchanged() throws Exception { assertEquals("gl\\?b", StringUtils.convertGlobToRegex("gl\\?b")); } @Test public void character_classes_dont_need_conversion() throws Exception { assertEquals("gl[-o]b", StringUtils.convertGlobToRegex("gl[-o]b")); } @Test public void escaped_classes_are_unchanged() throws Exception { assertEquals("gl\\[-o\\]b", StringUtils.convertGlobToRegex("gl\\[-o\\]b")); } @Test public void negation_in_character_classes() throws Exception { assertEquals("gl[^an!pz]b", StringUtils.convertGlobToRegex("gl[!an!pz]b")); } @Test public void nested_negation_in_character_classes() throws Exception { assertEquals("gl[[^an]!pz]b", StringUtils.convertGlobToRegex("gl[[!an]!pz]b")); } @Test public void escape_carat_if_it_is_the_first_char_in_a_character_class() throws Exception { assertEquals("gl[\\^o]b", StringUtils.convertGlobToRegex("gl[^o]b")); } @Test public void metachars_are_escaped() throws Exception { assertEquals("gl..*\\.\\(\\)\\+\\|\\^\\$\\@\\%b", StringUtils.convertGlobToRegex("gl?*.()+|^$@%b")); } @Test public void metachars_in_character_classes_dont_need_escaping() throws Exception { assertEquals("gl[?*.()+|^$@%]b", StringUtils.convertGlobToRegex("gl[?*.()+|^$@%]b")); } @Test public void escaped_backslash_is_unchanged() throws Exception { assertEquals("gl\\\\b", StringUtils.convertGlobToRegex("gl\\\\b")); } @Test public void slashQ_and_slashE_are_escaped() throws Exception { assertEquals("\\\\Qglob\\\\E", StringUtils.convertGlobToRegex("\\Qglob\\E")); } @Test public void braces_are_turned_into_groups() throws Exception { assertEquals("(glob|regex)", StringUtils.convertGlobToRegex("{glob,regex}")); } @Test public void escaped_braces_are_unchanged() throws Exception { assertEquals("\\{glob\\}", StringUtils.convertGlobToRegex("\\{glob\\}")); } @Test public void commas_dont_need_escaping() throws Exception { assertEquals("(glob,regex),", StringUtils.convertGlobToRegex("{glob\\,regex},")); } }
有几个库做类似Glob的模式匹配,比所列出的更现代:
Theresant目录扫描仪和泉AntPathMatcher
我推荐两种解决scheme,因为Ant风格Globbing几乎已经成为Java世界中的标准glob语法 (Hudson,Spring,Ant和我认为Maven)。
这是一个简单的Glob实现,处理*和? 在模式中
public class GlobMatch { private String text; private String pattern; public boolean match(String text, String pattern) { this.text = text; this.pattern = pattern; return matchCharacter(0, 0); } private boolean matchCharacter(int patternIndex, int textIndex) { if (patternIndex >= pattern.length()) { return false; } switch(pattern.charAt(patternIndex)) { case '?': // Match any character if (textIndex >= text.length()) { return false; } break; case '*': // * at the end of the pattern will match anything if (patternIndex + 1 >= pattern.length() || textIndex >= text.length()) { return true; } // Probe forward to see if we can get a match while (textIndex < text.length()) { if (matchCharacter(patternIndex + 1, textIndex)) { return true; } textIndex++; } return false; default: if (textIndex >= text.length()) { return false; } String textChar = text.substring(textIndex, textIndex + 1); String patternChar = pattern.substring(patternIndex, patternIndex + 1); // Note the match is case insensitive if (textChar.compareToIgnoreCase(patternChar) != 0) { return false; } } // End of pattern and text? if (patternIndex + 1 >= pattern.length() && textIndex + 1 >= text.length()) { return true; } // Go on to match the next character in the pattern return matchCharacter(patternIndex + 1, textIndex + 1); } }
我最近不得不这样做,并使用\Q
和\E
来逃避glob模式:
private static Pattern getPatternFromGlob(String glob) { return Pattern.compile( "^" + Pattern.quote(glob) .replace("*", "\\E.*\\Q") .replace("?", "\\E.\\Q") + "$"); }
来自雅加达ORO的 GlobCompiler / GlobEngine看上去很有希望。 它在Apache许可下可用。
与Tony Edgecombe的回答类似,这里是一个短小的简单的支持*
和?
没有使用正则expression式,如果有人需要一个。
public static boolean matches(String text, String glob) { String rest = null; int pos = glob.indexOf('*'); if (pos != -1) { rest = glob.substring(pos + 1); glob = glob.substring(0, pos); } if (glob.length() > text.length()) return false; // handle the part up to the first * for (int i = 0; i < glob.length(); i++) if (glob.charAt(i) != '?' && !glob.substring(i, i + 1).equalsIgnoreCase(text.substring(i, i + 1))) return false; // recurse for the part after the first *, if any if (rest == null) { return glob.length() == text.length(); } else { for (int i = glob.length(); i <= text.length(); i++) { if (matches(text.substring(i), rest)) return true; } return false; } }
我不知道“标准”实现,但是我知道在BSD许可下发布的sourceforge项目实现了文件的glob匹配。 它在一个文件中实现,也许你可以适应你的要求。
很久以前,我正在做一个巨大的glob驱动的文本过滤,所以我写了一小段代码(15行代码,除JDK之外没有任何依赖关系)。 它只处理'*'(对我来说是足够的),但可以很容易地扩展为'?'。 它比预编译的正则expression式快好几倍,不需要任何预编译(实质上每次模式匹配时都是string与string的比较)。
码:
public static boolean miniglob(String[] pattern, String line) { if (pattern.length == 0) return line.isEmpty(); else if (pattern.length == 1) return line.equals(pattern[0]); else { if (!line.startsWith(pattern[0])) return false; int idx = pattern[0].length(); for (int i = 1; i < pattern.length - 1; ++i) { String patternTok = pattern[i]; int nextIdx = line.indexOf(patternTok, idx); if (nextIdx < 0) return false; else idx = nextIdx + patternTok.length(); } if (!line.endsWith(pattern[pattern.length - 1])) return false; return true; } }
用法:
public static void main(String[] args) { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { // read from stdin space separated text and pattern for (String input = in.readLine(); input != null; input = in.readLine()) { String[] tokens = input.split(" "); String line = tokens[0]; String[] pattern = tokens[1].split("\\*+", -1 /* want empty trailing token if any */); // check matcher performance long tm0 = System.currentTimeMillis(); for (int i = 0; i < 1000000; ++i) { miniglob(pattern, line); } long tm1 = System.currentTimeMillis(); System.out.println("miniglob took " + (tm1-tm0) + " ms"); // check regexp performance Pattern reptn = Pattern.compile(tokens[1].replace("*", ".*")); Matcher mtchr = reptn.matcher(line); tm0 = System.currentTimeMillis(); for (int i = 0; i < 1000000; ++i) { mtchr.matches(); } tm1 = System.currentTimeMillis(); System.out.println("regexp took " + (tm1-tm0) + " ms"); // check if miniglob worked correctly if (miniglob(pattern, line)) { System.out.println("+ >" + line); } else { System.out.println("- >" + line); } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
从这里复制/粘贴
顺便说一下,看起来好像你在Perl中使用了这种方式
这在Perl中有诀窍:
my @files = glob("*.html") # Or, if you prefer: my @files = <*.html>