2009-08-08 23 views
68

是否有一個標準(最好是Apache Commons或類似的非病毒)庫,用於在Java中執行「glob」類型匹配?當我不得不在Perl中做類似的時候,我只是把所有的「.」改爲「\.」,「*」到「.*」和「?」到「.」等等,但我是想知道是否有人爲我完成了這項工作。是否存在與「glob」類型模式相同的java.util.regex?

類似的問題:Create regex from glob expression

+0

可不可以給你想要做什麼精確的例子嗎? – 2009-08-08 08:58:01

+0

我想做什麼(或者更確切地說我的客戶想做什麼)在網址中與「* -2009 /」或「* rss *」匹配。大多數情況下,轉換爲正則表達式非常簡單,但我想知道是否有更簡單的方法。 – 2009-08-08 10:50:02

+0

我推薦使用Ant風格的文件,因爲它似乎已經成爲Java世界中的典範。請參閱我的答案以獲取更多詳細信息:http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns/4038104#4038104。 – 2010-10-27 22:08:30

回答

33

沒有什麼內置的,但它是非常簡單的東西水珠般轉換爲正則表達式:

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; 
} 

這對我的作品,但我不知道這是否涵蓋了水珠「標準」如果有一個:)

更新保羅湯布林:我發現了一個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(); 
} 

我編輯成一本而不是自己做,因爲這個答案讓我走上正軌。

+0

是的,這幾乎是我提出的解決方案與最後一次我不得不這樣做(在Perl中),但我想知道是否有更優雅的東西。我想我會按你的方式去做。 – 2009-08-08 14:34:46

+0

實際上,我在Perl中發現了一個更好的實現,我可以在http://kobesearch.cpan.org/htdocs/Text-Glob/Text/Glob.pm.html – 2009-08-08 20:56:25

+0

中適應Java。難道你不能使用正則表達式替換把glob變成正則表達式? – 2009-08-09 01:10:49

2

我不知道「標準」的實施,但我知道這下實現的水珠匹配的文件BSD許可證發佈的SourceForge項目的。它在one file執行,也許你可以適應你的要求。

+0

更新後的鏈接:https://sourceforge.net/p/uncle/code/HEAD/tree/uncle/fileglob/trunk/src/com/uncle/fileglob/FileGlob.java – seanf 2016-04-11 07:24:03

43

通配是還計劃在Java 7

實現見FileSystem.getPathMatcher(String)the "Finding Files" tutorial

+14

奇妙。在地球上,這個實現僅限於「路徑」對象?!?在我的情況下,我想匹配URI ... – 2013-01-16 10:05:16

+1

在sun.nio的源代碼中對等,glob匹配似乎由[Globs.java]實現( http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/sun/nio/fs/Globs.java)不幸的是,這是專門爲文件系統路徑編寫的,所以它不能用於所有的字符串(它對路徑分隔符和非法字符做了一些假設),但它可能是一個有用的起點 – 2013-03-19 12:49:35

-1

順便說一句,它好像你沒有硬盤的方式在Perl

該做的伎倆在Perl:

my @files = glob("*.html") 
# Or, if you prefer: 
my @files = <*.html> 
+0

只有當glob是用於匹配文件。在perl的情況下,這些小球實際上來自一系列ip地址列表,這些地址是使用小球寫出的,因爲我不會介入,而在我目前的情況下,小球匹配的是urls。 – 2009-09-01 07:12:35

5

這是一個簡單的水珠實現它處理*和?在模式

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); 
    } 
} 
0

很久以前,我在做一個巨大的水珠驅動的文本過濾,所以我已經寫了一小段代碼(代碼15行,超出了JDK沒有依賴)。 它只處理'*'(對我來說是足夠的),但可以很容易地擴展爲'?'。 它比預編譯的正則表達式快幾倍,不需要任何預編譯(實質上每次模式匹配時都是字符串vs字符串比較)。

代碼:

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(); 
    } 
    } 

複製/粘貼從here

+0

由於它只有15行,因此在鏈接頁面出現故障時應將其包括在內。 – Raniz 2016-07-04 11:18:43

5

最近,我不得不這樣做,並使用\Q\E逃離glob模式:

private static Pattern getPatternFromGlob(String glob) { 
    return Pattern.compile(
    "^" + Pattern.quote(glob) 
      .replace("*", "\\E.*\\Q") 
      .replace("?", "\\E.\\Q") 
    + "$"); 
} 
+4

如果在字符串的某個位置有\ E,這不會中斷嗎? – jmo 2011-01-12 15:29:51

+0

@jmo,是的,但是你可以用glob = Pattern.quote(glob)預處理'glob'變量,我相信它可以處理這種邊緣情況。但是,在這種情況下,您不需要預先添加並追加第一個和最後一個\\ Q和\\ E。 – 2016-04-20 21:30:55

+2

@jmo我已經修復了使用Pattern.quote()的例子。 – dimo414 2016-12-29 23:24:19

3

類似於Tony Edgecombeanswer,這裏是一個短而簡單的globber,支持*?而不使用正則表達式,如果有人需要的話。

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; 
    } 
} 
+0

優秀的回答tihi!這很簡單,可以在快速閱讀中理解,而不是太令人困惑:-) – 2015-03-10 20:49:32

8

有幾個是做水珠狀的圖案匹配的是更現代的比那些上市庫:

即使世界螞蟻Directory Scanner 而 泉AntPathMatcher

我建議雙方在其他自從Ant Style Globbing幾乎已經成爲Java世界中的標準glob語法(Hudson,Spring,Ant和我認爲Maven)。

+1

以下是使用AntPathMatcher進行工件的Maven座標:https://search.maven.org/#search%7Cgav%7C1%7Cg%3A% 22org.springframework%22%20AND%20a%3A%22spring-core%22 以及一些使用樣本的測試:https://github.com/spring-projects/spring-framework/blob/master/spring-core/ src/test/java/org/springframework/util/AntPathMatcherTests.java – seanf 2016-04-11 08:01:42

+0

而且你可以自定義「路徑」字符......所以它對路徑以外的東西很有用...... – 2016-09-21 08:12:26

20

感謝大家在這裏的貢獻。我寫了一個更全面的轉換比以前的答案:

/** 
* 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(); 
} 

而且單元測試,以證明它的工作原理:

/** 
* @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[^a-n!p-z]b", StringUtils.convertGlobToRegex("gl[!a-n!p-z]b")); 
    } 

    @Test 
    public void nested_negation_in_character_classes() throws Exception { 
     assertEquals("gl[[^a-n]!p-z]b", StringUtils.convertGlobToRegex("gl[[!a-n]!p-z]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?*.()+|^[email protected]%b")); 
    } 

    @Test 
    public void metachars_in_character_classes_dont_need_escaping() throws Exception { 
     assertEquals("gl[?*.()+|^[email protected]%]b", StringUtils.convertGlobToRegex("gl[?*.()+|^[email protected]%]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},")); 
    } 

}