"glob"유형 패턴에 대한 java.util.regex에 해당하는 것이 있습니까?


84

Java에서 "glob"유형 일치를 수행하기위한 표준 (가급적 Apache Commons 또는 유사하게 비 바이러스 성) 라이브러리가 있습니까? Perl에서 비슷한 작업을 한 번 수행해야했을 때 " ."를 " \."로, " *"를 " .*"로, " ?"를 " ."로 변경했습니다.하지만 누군가이 작업을 수행했는지 궁금합니다. 나를 위해 일하십시오.

비슷한 질문 : glob 표현식에서 정규식 만들기


GlobCompiler / GlobEngine 에서, 자카르타 ORO는 , 외모는 약속. Apache 라이선스에 따라 사용할 수 있습니다.
Steve Trout

당신이하고 싶은 일의 정확한 예를 들어 주실 수 있습니까?
Thorbjørn Ravn Andersen

내가하고 싶은 것 (또는 내 고객이 원하는 것)은 URL에서 " -2009 /"또는 "* rss " 와 같은 것과 일치하는 것 입니다. 대부분 정규식으로 변환하는 것은 매우 사소하지만 더 쉬운 방법이 있는지 궁금합니다.
Paul Tomblin

Java 세계에서 표준 globing이 된 것처럼 보이는 Ant 스타일 파일 globing을 권장합니다. 자세한 내용은 내 대답을 참조하십시오 : stackoverflow.com/questions/1247772/… .
Adam Gent

1
@BradMace, 관련이 있지만 대부분의 답변은 디렉토리 트리를 순회한다고 가정합니다. 그래도 누군가가 여전히 임의의 문자열에 대해 glob 스타일 일치를 수행하는 방법을 찾고 있다면 아마도 그 대답도 살펴 봐야 할 것입니다.
Paul Tomblin 2012 년

답변:


46

내장 된 것은 없지만 glob과 같은 것을 정규식으로 변환하는 것은 매우 간단합니다.

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

이것은 나를 위해 작동하지만 글롭 "표준"을 포함하는지 확실하지 않습니다.

Paul Tomblin의 업데이트 : glob 변환을 수행하는 펄 프로그램을 발견하고이를 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();
}

이 답변이 나를 올바른 길로 인도하기 때문에 내 답변을 만들기보다는이 답변을 편집하고 있습니다.


1
그래, 그것은 내가 마지막으로 (Perl에서)해야 할 때 생각해 낸 거의 해결책이지만 더 우아한 것이 있는지 궁금합니다. 나는 당신의 방식으로 그것을 할 것이라고 생각합니다.
Paul Tomblin


정규식 교체를 사용하여 glob을 정규식으로 바꿀 수 없습니까?
Tim Sylvester

1
String.match가 전체 문자열에 대해서만
일치

10
참고 : 'globbing'의 표준은 POSIX Shell 언어입니다 -opengroup.org/onlinepubs/009695399/utilities/…
Stephen C

60

Globbing은 Java 7 에서도 구현 될 예정입니다 .

FileSystem.getPathMatcher(String)"파일 찾기"튜토리얼을 참조하십시오 .


23
기이. 그러나 왜이 구현은 "Path"객체로 제한됩니까?!? 제 경우에는 URI를 일치시키고 싶습니다 ...
Yves Martin

3
sun.nio의 소스를 살펴보면 glob 매칭이 Globs.java에 의해 구현 된 것처럼 보입니다 . 불행히도 이것은 파일 시스템 경로를 위해 특별히 작성되었으므로 모든 문자열에 사용할 수는 없습니다 (경로 구분 기호와 잘못된 문자에 대해 몇 가지 가정을합니다). 그러나 도움이되는 출발점이 될 수 있습니다.
Neil Traft 2013 년

33

기여해 주신 모든 분들께 감사드립니다. 이전 답변보다 더 포괄적 인 변환을 작성했습니다.

/**
 * 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?*.()+|^$@%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},"));
    }

}

이 코드에 감사드립니다, Neil! 오픈 소스 라이선스를 기꺼이 주시겠습니까?
Steven

1
나는이 답변의 코드가 공개 도메인에 있음을 승인합니다.
Neil Traft

다른 조치를 취해야합니까? - P
닐 Traft

9

나열된 것보다 더 현대적인 Glob 유사 패턴 일치를 수행하는 라이브러리가 몇 개 있습니다.

Theres Ants 디렉토리 스캐너 및 Springs AntPathMatcher

나는 다른 솔루션보다 둘 다 권장합니다. Ant Style Globbing이 Java 세계 (Hudson, Spring, Ant 및 Maven) 에서 표준 glob 구문이 .


1
다음은 AntPathMatcher를 사용한 아티팩트의 Maven 좌표입니다. search.maven.org/… 그리고 샘플 사용을 사용한 일부 테스트 : github.com/spring-projects/spring-framework/blob/master/…
seanf

이 ... 경로 이외의 것들에 대한 유용한 그래서 당신은 ... "경로"문자를 사용자 정의 할 수 있습니다
마이클 와일즈

7

나는 최근에 그것을 사용 \Q하고 \Eglob 패턴을 탈출 해야했습니다 .

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

4
문자열 어딘가에 \ E가 있으면 깨지지 않습니까?
jmo

@jmo, 예,하지만 globglob = Pattern.quote (glob)로 변수를 사전 처리하여이를 피할 수 있습니다 . 그러나이 경우 첫 번째와 마지막 \\ Q 및 \\ E를 앞에 추가하고 추가 할 필요가 없습니다.
Kimball Robinson

2
@jmo Pattern.quote ()를 사용하도록 예제를 수정했습니다.
dimo414

5

이것은 * 및?를 처리하는 간단한 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);
    }
}

5

Tony Edgecombe답변 과 유사하게 누구나 필요하다면 정규식 을 지원 *하고 ?사용하지 않는 짧고 간단한 글로버가 있습니다.

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

1
훌륭한 대답 tihi! 이것은 빠른 읽기로 이해하기에 충분히 간단하며 너무 당황하지 않습니다. :-)
Limited Atonement

3

약간 해키 한 접근 방식 일 수 있습니다. 나는 NIO2의 Files.newDirectoryStream(Path dir, String glob)코드 에서 그것을 알아 냈다 . 모든 일치하는 새 Path개체가 생성 된다는 점에주의하십시오 . 지금까지는 Windows FS에서만 테스트 할 수 있었지만 Unix에서도 작동해야한다고 생각합니다.

// a file system hack to get a glob matching
PathMatcher matcher = ("*".equals(glob)) ? null
    : FileSystems.getDefault().getPathMatcher("glob:" + glob);

if ("*".equals(glob) || matcher.matches(Paths.get(someName))) {
    // do you stuff here
}

업데이트는 Mac과 Linux 모두에서 작동합니다.



0

오래 전에 저는 대규모 글롭 기반 텍스트 필터링을 수행하고 있었기 때문에 작은 코드 조각을 작성했습니다 (코드 15 줄, JDK 이상의 종속성 없음). '*'만 처리하지만 (나에게 충분 함) '?'에 대해서는 쉽게 확장 할 수 있습니다. 사전 컴파일 된 정규 표현식보다 몇 배 빠르며 사전 컴파일이 필요하지 않습니다 (본질적으로 패턴이 일치 할 때마다 문자열 대 문자열 비교).

암호:

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

여기 에서 복사 / 붙여 넣기


15 줄 밖에되지 않기 때문에 링크 된 페이지가 다운 될 경우를 대비하여 여기에 포함시켜야합니다.
Raniz

0

Vincent Robert / dimo414 의 이전 솔루션 은 API에 문서화되지 않았으므로 다른 / 향후 Java 구현의 경우가 아닐 수있는 ... Pattern.quote()측면에서 구현에 의존합니다 . 다음 솔루션은를 사용 하는 대신 모든 발생을 이스케이프하여 구현 종속성을 제거합니다 . 일치시킬 문자열에 줄 바꿈이 포함 된 경우 모드 ( ) 도 활성화 됩니다 .\Q\E\Equote()DOTALL(?s)

    public static Pattern globToRegex(String glob)
    {
        return Pattern.compile(
            "(?s)^\\Q" +
            glob.replace("\\E", "\\E\\\\E\\Q")
                .replace("*", "\\E.*\\Q")
                .replace("?", "\\E.\\Q") +
            "\\E$"
        );
    }

-1

그건 그렇고, Perl에서 힘들게 한 것처럼 보입니다.

이것은 Perl에서 트릭을 수행합니다.

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

1
glob이 일치하는 파일 인 경우에만 작동합니다. perl의 경우 glob은 실제로 glob을 사용하여 작성된 ip 주소 목록에서 나왔고, 현재의 경우 glob은 URL과 일치해야했습니다.
Paul Tomblin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.