java glob

¿Hay un equivalente de java.util.regex para patrones de tipo "glob"?



(11)

¿Existe una biblioteca estándar (preferiblemente Apache Commons o similarmente no viral) para hacer coincidencias de tipo "glob" en Java? Cuando tuve que hacer algo similar en Perl una vez, cambié todo el " . " A " /. ", el " * " a " .* " Y el " ? " A " . " Y ese tipo de cosas, pero Me pregunto si alguien hizo el trabajo por mí.

Pregunta similar: Crear expresión regular desde la expresión glob


Esta es una implementación simple de Glob que maneja * y? en el patrón

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



Gracias a todos aquí por sus contribuciones. Escribí una conversión más completa que cualquiera de las respuestas anteriores:

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

Y la unidad prueba para probar que funciona:

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


Hace mucho tiempo, estaba haciendo un filtrado de texto global masivo, así que escribí un pequeño fragmento de código (15 líneas de código, sin dependencias más allá de JDK). Solo maneja ''*'' (fue suficiente para mí), pero se puede extender fácilmente para ''?''. Es varias veces más rápido que la expresión regular precompilada, no requiere ninguna compilación previa (esencialmente, es una comparación de cadena frente a cadena cada vez que se combina el patrón).

Código:

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

Uso:

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

Copiar / pegar desde here


Hay un par de bibliotecas que hacen patrones similares a Glob que son más modernos que los enumerados:

Theres Ants Directory Scanner And Springs AntPathMatcher

Recomiendo tanto las otras soluciones como Ant Style Globbing se ha convertido prácticamente en la sintaxis glob estándar en el mundo de Java (Hudson, Spring, Ant y creo que Maven).


No hay nada incorporado, pero es bastante simple convertir algo parecido a un globo terráqueo a una expresión regular:

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

esto funciona para mí, pero no estoy seguro si cubre el glob "estándar", si hay uno :)

Actualización de Paul Tomblin: Encontré un programa perl que hace la conversión glob, y al adaptarlo a Java, termino con:

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

Estoy editando esta respuesta en lugar de hacer la mía porque esta respuesta me puso en el camino correcto.


No sé acerca de una implementación "estándar", pero sé de un proyecto de sourceforge publicado bajo la licencia BSD que implementó la correspondencia global para los archivos. Se implementa en un archivo , tal vez puede adaptarlo a sus requisitos.


Por cierto, parece que lo hiciste de la manera difícil en Perl

Esto hace el truco en Perl:

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


Recientemente tuve que hacerlo y usé /Q y /E para escapar del patrón de glob:

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


Similar a la answer , aquí hay un globber corto y simple que admite * y ? sin usar expresiones regulares, si alguien necesita uno.

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