¿Cómo encontrar archivos que coincidan con una cadena comodín en Java?

157

Esto debería ser realmente simple. Si tengo una cadena como esta:

../Test?/sample*.txt

entonces, ¿cuál es una forma generalmente aceptada de obtener una lista de archivos que coinciden con este patrón? (por ejemplo, debe coincidir ../Test1/sample22b.txty ../Test4/sample-spiffy.txtno ../Test3/sample2.blaho ../Test44/sample2.txt)

He echado un vistazo org.apache.commons.io.filefilter.WildcardFileFiltery parece la bestia correcta, pero no estoy seguro de cómo usarlo para encontrar archivos en una ruta de directorio relativa.

Supongo que puedo buscar la fuente de hormiga ya que usa la sintaxis comodín, pero debo estar perdiendo algo bastante obvio aquí.

( editar : el ejemplo anterior fue solo un caso de ejemplo. Estoy buscando la forma de analizar rutas generales que contienen comodines en tiempo de ejecución. Descubrí cómo hacerlo basándose en la sugerencia de mmyers, pero es un poco molesto. Sin mencionar que el JRE de Java parece analizar automáticamente los comodines simples en los principales (argumentos de Cadena []) desde un solo argumento para "ahorrarme" tiempo y molestias ... Me alegra no haber tenido argumentos que no sean de archivo en el mezcla.)

Jason S
fuente
2
Ese es el shell que analiza los comodines, no Java. Puede escapar de ellos, pero el formato exacto depende de su sistema.
Michael Myers
2
No, no es. Windows no analiza * comodines. He comprobado esto ejecutando la misma sintaxis en un archivo por lotes ficticio e imprimiendo el argumento # 1 que era Test / *. Obj apuntando a un directorio lleno de archivos .obj. Imprime "Prueba / *. Obj". Java parece hacer algo raro aquí.
Jason S
Huh, tienes razón; casi todos los comandos de shell incorporados expanden comodines, pero el shell en sí no. De todos modos, puede poner el argumento entre comillas para evitar que Java analice los comodines: java MyClass "Test / *. Obj"
Michael Myers
3
Más de 6 años más tarde, para aquellos que detestan el desplazamiento y quieren la solución Java> = 7 zero-dep, vea y vote a favor la respuesta a continuación por @Vadzim, o poros / poros verbosely
earcam

Respuestas:

81

Considere DirectoryScanner de Apache Ant:

DirectoryScanner scanner = new DirectoryScanner();
scanner.setIncludes(new String[]{"**/*.java"});
scanner.setBasedir("C:/Temp");
scanner.setCaseSensitive(false);
scanner.scan();
String[] files = scanner.getIncludedFiles();

Deberá hacer referencia a ant.jar (~ 1.3 MB para ant 1.7.1).

Misha
fuente
1
¡excelente! por cierto, scanner.getIncludedDirectories () hace lo mismo si necesita directorios. (getIncludedFiles no funcionará)
Tilman Hausherr
1
El proyecto comodín en github también funciona como un encanto: github.com/EsotericSoftware/wildcard
Moreaki
1
@Moreaki que pertenece como una respuesta separada, no como un comentario
Jason S
Esto mismo DirectoryScannerse encuentra en plexus-utils (241Kb). Que es más pequeño que ant.jar(1.9Mb).
Verhagen
Esto funciona. Pero parece ser extremadamente lento en comparación con un lscon el mismo patrón de archivo (milisegundos usando ls <pattern>vs. minutos cuando usa el DirectoryScanner) ...
dokaspar
121

Pruebe FileUtilscon Apache commons-io ( listFilesy iterateFilesmétodos):

File dir = new File(".");
FileFilter fileFilter = new WildcardFileFilter("sample*.java");
File[] files = dir.listFiles(fileFilter);
for (int i = 0; i < files.length; i++) {
   System.out.println(files[i]);
}

Para resolver su problema con las TestXcarpetas, primero iteraría a través de la lista de carpetas:

File[] dirs = new File(".").listFiles(new WildcardFileFilter("Test*.java");
for (int i=0; i<dirs.length; i++) {
   File dir = dirs[i];
   if (dir.isDirectory()) {
       File[] files = dir.listFiles(new WildcardFileFilter("sample*.java"));
   }
}

Toda una solución de 'fuerza bruta' pero debería funcionar bien. Si esto no se ajusta a sus necesidades, siempre puede usar RegexFileFilter .

Vladimir
fuente
2
Bien, ahora has llegado exactamente a donde estaba Jason S cuando publicó la pregunta.
Michael Myers
no exactamente. También está el RegexFileFilter que se puede usar (pero personalmente nunca tuve la necesidad de hacerlo).
Vladimir
57

Estos son ejemplos de archivos de listado por patrón con tecnología Java 7 nio globbing y Java 8 lambdas:

    try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(
            Paths.get(".."), "Test?/sample*.txt")) {
        dirStream.forEach(path -> System.out.println(path));
    }

o

    PathMatcher pathMatcher = FileSystems.getDefault()
        .getPathMatcher("regex:Test./sample\\w+\\.txt");
    try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(
            new File("..").toPath(), pathMatcher::matches)) {
        dirStream.forEach(path -> System.out.println(path));
    }
Vadzim
fuente
13
OFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
amoebe
@Qstnr_La, sí, excepto lambdas auxiliares y referencias de métodos.
Vadzim
29

Puede convertir su cadena comodín en una expresión regular y usarla con el matchesmétodo de String . Siguiendo tu ejemplo:

String original = "../Test?/sample*.txt";
String regex = original.replace("?", ".?").replace("*", ".*?");

Esto funciona para tus ejemplos:

Assert.assertTrue("../Test1/sample22b.txt".matches(regex));
Assert.assertTrue("../Test4/sample-spiffy.txt".matches(regex));

Y contraejemplos:

Assert.assertTrue(!"../Test3/sample2.blah".matches(regex));
Assert.assertTrue(!"../Test44/sample2.txt".matches(regex));
Fabian Steeg
fuente
3
Esto no funcionará para archivos que contengan caracteres especiales de expresiones regulares como (, + o $
djjeck el
Usé 'String regex = "^" + s.replace ("?", ".?"). Replace (" ", ". ?") + "$"' (Los asteriscos desaparecieron en mi comentario por alguna razón. ..)
Jouni Aro
2
¿Por qué reemplazar * con '. *? ? public static boolean isFileMatchTargetFilePattern (Archivo final f, String final targetPattern) {`` String regex = targetPattern.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Tony el
Como el OP solicitó "rutas generales que contengan comodines", tendría que citar más caracteres especiales. Prefiero usar Pattern.quote:StringBuffer regexBuffer = ...; Matcher matcher = Pattern.compile("(.*?)([*?])").matcher(original); while (matcher.find()) { matcher.appendReplacement(regexBuffer, (Pattern.quote(matcher.group(1)) + (matcher.group(2).equals("*") ? ".*?" : ".?")).replace("\\", "\\\\").replace("$", "\\$")); } matcher.appendTail(regexBuffer);
EndlosSchleife
Anexo: "?" denota un carácter obligatorio, por lo que debe reemplazarse con en .lugar de .?.
EndlosSchleife
23

Desde Java 8 puede usar el Files#findmétodo directamente desde java.nio.file.

public static Stream<Path> find(Path start,
                                int maxDepth,
                                BiPredicate<Path, BasicFileAttributes> matcher,
                                FileVisitOption... options)

Ejemplo de uso

Files.find(startingPath,
           Integer.MAX_VALUE,
           (path, basicFileAttributes) -> path.toFile().getName().matches(".*.pom")
);
Grzegorz Gajos
fuente
1
¿Puedes extender el ejemplo para decir imprimir la ruta de la primera coincidencia guardada en la transmisión?
jxramos
18

Es posible que no lo ayude en este momento, pero JDK 7 está diseñado para que el nombre de archivo glob y regex coincida como parte de "Más características de NIO".

Tom Hawtin - tackline
fuente
3
En Java 7: Files.newDirectoryStream (ruta, patrón global)
Pat Niemeyer
13

La biblioteca de comodines realiza eficientemente la coincidencia de nombres de archivo glob y regex:

http://code.google.com/p/wildcard/

La implementación es sucinta: JAR tiene solo 12.9 kilobytes.

Nates
fuente
2
La única desventaja es que no está en Maven Central
yegor256
3
Es OSS, adelante y póngalo en Maven Central. :)
Nates
10

Manera simple sin usar ninguna importación externa es usar este método

Creé archivos csv nombrados con billing_201208.csv, billing_201209.csv, billing_201210.csv y parece que funciona bien.

La salida será la siguiente si los archivos enumerados anteriormente existen

found billing_201208.csv
found billing_201209.csv
found billing_201210.csv

    // Use Importar -> import java.io.File
        public static void main (String [] args) {
        String pathToScan = ".";
        String target_file; // fileThatYouWantToFilter
        File folderToScan = nuevo archivo (pathToScan); 

    File[] listOfFiles = folderToScan.listFiles();

     for (int i = 0; i < listOfFiles.length; i++) {
            if (listOfFiles[i].isFile()) {
                target_file = listOfFiles[i].getName();
                if (target_file.startsWith("billing")
                     && target_file.endsWith(".csv")) {
                //You can add these files to fileList by using "list.add" here
                     System.out.println("found" + " " + target_file); 
                }
           }
     }    
}

Umair Aziz
fuente
6

Como se publicó en otra respuesta, la biblioteca de comodines funciona tanto para la coincidencia de nombres de globo y expresiones regulares: http://code.google.com/p/wildcard/

Utilicé el siguiente código para hacer coincidir patrones globales que incluyen absoluto y relativo en los sistemas de archivos de estilo * nix:

String filePattern = String baseDir = "./";
// If absolute path. TODO handle windows absolute path?
if (filePattern.charAt(0) == File.separatorChar) {
    baseDir = File.separator;
    filePattern = filePattern.substring(1);
}
Paths paths = new Paths(baseDir, filePattern);
List files = paths.getFiles();

Pasé un tiempo tratando de obtener los métodos FileUtils.listFiles en la biblioteca io de Apache commons (vea la respuesta de Vladimir) para hacer esto, pero no tuve éxito (ahora me doy cuenta / creo que solo puede manejar patrones que coinciden con un directorio o archivo a la vez) .

Además, el uso de filtros regex (ver la respuesta de Fabian) para procesar patrones globales glob de tipo absoluto proporcionados por el usuario sin buscar en todo el sistema de archivos requeriría un cierto preprocesamiento del glob suministrado para determinar el prefijo más grande no regex / glob.

Por supuesto, Java 7 puede manejar bien la funcionalidad solicitada, pero desafortunadamente estoy atascado con Java 6 por ahora. La biblioteca es relativamente minúscula con un tamaño de 13.5 kb.

Nota para los revisores: Intenté agregar lo anterior a la respuesta existente que menciona esta biblioteca, pero la edición fue rechazada. Tampoco tengo suficiente representante para agregar esto como comentario. ¿No hay una mejor manera ...

Oliver Coleman
fuente
¿Planea migrar su proyecto a otro lugar? Ver code.google.com/p/support/wiki/ReadOnlyTransition
Luc M
1
No es mi proyecto, y parece que ya se ha migrado: github.com/EsotericSoftware/wildcard
Oliver Coleman
5

Deberías poder usar el WildcardFileFilter. Solo use System.getProperty("user.dir")para obtener el directorio de trabajo. Prueba esto:

public static void main(String[] args) {
File[] files = (new File(System.getProperty("user.dir"))).listFiles(new WildcardFileFilter(args));
//...
}

No debería ser necesario reemplazar *con [.*], suponiendo que utiliza el filtro de comodín java.regex.Pattern. No he probado esto, pero uso patrones y filtros de archivos constantemente.

Anónimo
fuente
3

El filtro Apache está diseñado para iterar archivos en un directorio conocido. Para permitir los comodines en el directorio también, tendría que dividir la ruta en ' \' o ' /' y hacer un filtro en cada parte por separado.

Michael Myers
fuente
1
Esto funcionó. Fue un poco molesto, pero no particularmente propenso a problemas. Sin embargo, espero con interés las características de JDK7 para la coincidencia global.
Jason S
0

¿Por qué no usar hacer algo como:

File myRelativeDir = new File("../../foo");
String fullPath = myRelativeDir.getCanonicalPath();
Sting wildCard = fullPath + File.separator + "*.txt";

// now you have a fully qualified path

Entonces no tendrá que preocuparse por los caminos relativos y puede hacer su comodín según sea necesario.

Elijah
fuente
1
Porque la ruta relativa también puede tener comodines.
Jason S
0

Método Util:

public static boolean isFileMatchTargetFilePattern(final File f, final String targetPattern) {
        String regex = targetPattern.replace(".", "\\.");  //escape the dot first
        regex = regex.replace("?", ".?").replace("*", ".*");
        return f.getName().matches(regex);

    }

Prueba de unidad:

@Test
public void testIsFileMatchTargetFilePattern()  {
    String dir = "D:\\repository\\org\my\\modules\\mobile\\mobile-web\\b1605.0.1";
    String[] regexPatterns = new String[] {"_*.repositories", "*.pom", "*-b1605.0.1*","*-b1605.0.1", "mobile*"};
    File fDir = new File(dir);
    File[] files = fDir.listFiles();

    for (String regexPattern : regexPatterns) {
        System.out.println("match pattern [" + regexPattern + "]:");
        for (File file : files) {
            System.out.println("\t" + file.getName() + " matches:" + FileUtils.isFileMatchTargetFilePattern(file, regexPattern));
        }
    }
}

Salida:

match pattern [_*.repositories]:
    mobile-web-b1605.0.1.pom matches:false
    mobile-web-b1605.0.1.war matches:false
    _remote.repositories matches:true
match pattern [*.pom]:
    mobile-web-b1605.0.1.pom matches:true
    mobile-web-b1605.0.1.war matches:false
    _remote.repositories matches:false
match pattern [*-b1605.0.1*]:
    mobile-web-b1605.0.1.pom matches:true
    mobile-web-b1605.0.1.war matches:true
    _remote.repositories matches:false
match pattern [*-b1605.0.1]:
    mobile-web-b1605.0.1.pom matches:false
    mobile-web-b1605.0.1.war matches:false
    _remote.repositories matches:false
match pattern [mobile*]:
    mobile-web-b1605.0.1.pom matches:true
    mobile-web-b1605.0.1.war matches:true
    _remote.repositories matches:false
Tony
fuente
no puede simplemente usar la búsqueda de texto con rutas del sistema de archivos; de lo contrario foo/bar.txtcoincide foo?bar.txty eso no es correcto
Jason S
Jason utilicé file.getName () que no contiene la ruta.
Tony
entonces no funciona para el patrón de ejemplo que di:../Test?/sample*.txt
Jason S
0
Path testPath = Paths.get("C:\");

Stream<Path> stream =
                Files.find(testPath, 1,
                        (path, basicFileAttributes) -> {
                            File file = path.toFile();
                            return file.getName().endsWith(".java");
                        });

// Print all files found
stream.forEach(System.out::println);
Anatoliy Shuba
fuente