"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.