"Glob" türü modeller için java.util.regex'in eşdeğeri var mı?


87

Java'da "glob" tipi eşleşmeler yapmak için standart (tercihen Apache Commons veya benzer şekilde viral olmayan) bir kitaplık var mı? Perl'de bir kez benzer bir şey yapmak zorunda kaldığımda, tüm " ." olarak " \.", " *" olarak " .*" ve " ?" için " ." gibi şeyleri ve bu tür şeyleri değiştirdim, ama merak ediyorum benim için çalış.

Benzer soru: Glob ifadesinden normal ifade oluşturma


GlobCompiler / GlobEngine dan, Cakarta ORO , bakışlar umut verici. Apache Lisansı altında mevcuttur.
Steve Trout

Ne yapmak istediğinize dair kesin bir örnek verebilir misiniz?
Thorbjørn Ravn Andersen

Yapmak istediğim (veya daha doğrusu müşterimin yapmak istediği şey) url'lerde " -2009 /" veya "* rss " gibi şeyleri eşleştirmek . Çoğunlukla normal ifadeye dönüştürmek oldukça önemsizdir, ancak daha kolay bir yol olup olmadığını merak ettim.
Paul Tomblin

Java dünyasında kanonik genelleme haline gelmiş gibi göründüğü için Ant stili dosya globlamasını öneriyorum. Daha fazla ayrıntı için cevabıma bakın: stackoverflow.com/questions/1247772/… .
Adam Gent

1
@BradMace, ilgili ancak buradaki cevapların çoğu bir dizin ağacını geçtiğinizi varsayıyor. Yine de, herhangi biri hala rasgele dizelerin glob stili eşleştirmesinin nasıl yapılacağını arıyorsa, muhtemelen bu yanıta da bakmalıdır.
Paul Tomblin

Yanıtlar:


47

Yerleşik bir şey yoktur, ancak glob benzeri bir şeyi normal ifadeye dönüştürmek oldukça kolaydır:

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

bu benim için çalışıyor, ancak glob "standardını" kapsayıp kapsamadığından emin değilim, eğer varsa :)

Paul Tomblin tarafından güncelleme: Glob dönüşümü yapan ve onu Java'ya uyarlayan bir perl programı buldum:

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

Kendi cevabım yerine bu cevabı düzeltiyorum çünkü bu cevap beni doğru yola koydu.


1
Evet, bunu en son yapmak zorunda kaldığımda (Perl'de) bulduğum çözüm buydu ama daha zarif bir şey olup olmadığını merak ediyordum. Sanırım senin yöntemini yapacağım.
Paul Tomblin


Bir glob'u normal ifadeye dönüştürmek için normal ifade yerine kullanamaz mısınız?
Tim Sylvester

1
Baştaki ve sondaki '*' karakterlerini
ayıran üstteki satırlar,

10
Bilginize: 'Globbing' standardı, POSIX Kabuk dilidir - opengroup.org/onlinepubs/009695399/utilities/…
Stephen C

62

Globbing'in Java 7'de de uygulanması planlanıyor .

Bkz. FileSystem.getPathMatcher(String)Ve "Dosyaları Bulma" eğiticisine .


23
Harikulade. Ama neden bu uygulama "Yol" nesneleriyle sınırlı?!? Benim durumumda, URI ile eşleşmek istiyorum ...
Yves Martin

3
Sun.nio'nun kaynağına bakıldığında, glob eşlemesi Globs.java tarafından uygulanıyor gibi görünüyor . Ne yazık ki, bu özellikle dosya sistemi yolları için yazılmıştır, bu nedenle tüm dizeler için kullanılamaz (yol ayırıcılar ve geçersiz karakterler hakkında bazı varsayımlar yapar). Ancak yararlı bir başlangıç ​​noktası olabilir.
Neil Traft

33

Katkılarından dolayı buradaki herkese teşekkürler. Önceki cevaplardan daha kapsamlı bir dönüşüm yazdım:

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

Ve çalıştığını kanıtlamak için birim test eder:

/**
 * @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},"));
    }

}

Bu kod için teşekkürler Neil! Açık kaynak lisansı vermeye istekli misiniz?
Steven

1
Bu yanıttaki kodun kamu malı olduğunu kabul ediyorum.
Neil Traft

Başka bir şey yapmalı mıyım? :-P
Neil Traft

9

Glob benzeri desen eşleştirmesi yapan ve listelenenlerden daha modern olan birkaç kitaplık vardır:

Karıncalar Dizin Tarayıcısı Ve Yayları AntPathMatcher Var

Ant Style Globbing Java dünyasında hemen hemen standart glob sözdizimi haline geldiğinden (Hudson, Spring, Ant ve sanırım Maven) diğer çözümlerin ikisini de öneriyorum .


1
İşte AntPathMatcher ile yapıt için Maven koordinatları: search.maven.org/… Ve örnek kullanımlı bazı testler: github.com/spring-projects/spring-framework/blob/master/…
seanf

Ve "yol" karakterini özelleştirebilirsiniz ... böylece yollar dışındaki şeyler için kullanışlıdır ...
Michael Wiles

7

Geçenlerde bunu yapmak zorunda kaldım ve kullandım \Qve \Eglob modelinden kaçmak zorunda kaldım :

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

4
Dizide bir yerde \ E varsa bu kırılmaz mı?
jmo

@jmo, evet, ancak bu globtür uç durumları ele aldığına inandığım glob = Pattern.quote (glob) ile değişkeni önceden işleyerek bunu aşabilirsiniz. Bu durumda, yine de, ilk ve son \\ Q ve \\ E'yi başına ve sonuna eklemenize gerek yoktur.
Kimball Robinson

2
@jmo Pattern.quote () kullanmak için örneği düzelttim.
dimo414

5

Bu, * ve? İşleyen basit bir Glob uygulamasıdır. modelde

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

Benzer Tony Edgecombe 'ın cevabı , burada kısa ve basit globber olduğunu destekler *ve ?herkes ihtiyacı olduğunda, normal ifade kullanmadan.

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
Mükemmel cevap tihi! Bu, hızlı bir okumada anlaşılacak kadar basit ve çok şaşırtıcı değil :-)
lmat - Monica'yı yeniden kur

3

Biraz karmaşık bir yaklaşım olabilir. Bunu NIO2'nin Files.newDirectoryStream(Path dir, String glob)kodundan anladım . Her eşleşen yeni Pathnesnenin oluşturulduğuna dikkat edin . Şimdiye kadar bunu yalnızca Windows FS'de test edebildim, ancak Unix'te de çalışması gerektiğine inanıyorum.

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

GÜNCELLEME Hem Mac hem de Linux'ta çalışır.



0

Uzun zaman önce devasa bir küresel tabanlı metin filtreleme yapıyordum, bu yüzden küçük bir kod parçası yazdım (15 satır kod, JDK dışında bağımlılık yok). Yalnızca '*' (benim için yeterliydi) işliyor ancak '?' Önceden derlenmiş regexp'ten birkaç kat daha hızlıdır, herhangi bir ön derleme gerektirmez (esasen bu, model her eşleştiğinde bir dizge-dizge karşılaştırmasıdır).

Kod:

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

Kullanım:

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

Buradan kopyalayın / yapıştırın


Sadece 15 satır olduğundan, bağlantılı sayfanın aşağı inmesi durumunda buraya eklemelisiniz.
Raniz

0

Bir önceki çözüm Vincent Robert tarafından / üzerinde dimo414 dayanır Pattern.quote()açısından uygulanıyor \Q...\E , API belgelenmiştir hangi ve bu nedenle diğer / gelecek Java uygulamaları için geçerli olmayabilir. Aşağıdaki çözüm \E, kullanmak yerine tüm oluşumlardan kaçarak bu uygulama bağımlılığını ortadan kaldırır quote(). Eşleştirilecek dizenin yeni satırlar içermesi durumunda DOTALLmode ( (?s)) 'yi de etkinleştirir .

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

-2

Bu arada, Perl'de zor yoldan yapmışsın gibi görünüyor.

Perl'deki hile şu:

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

1
Bu sadece, glob dosyaları eşleştirmek için ise işe yarar. Perl durumunda, globlar aslında, girmeyeceğim nedenlerle globlar kullanılarak yazılmış bir ip adresleri listesinden geliyordu ve şu anki durumumda globlar url'lerle eşleşecekti.
Paul Tomblin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.