Comment trouver des fichiers qui correspondent à une chaîne générique en Java?

157

Cela devrait être très simple. Si j'ai une chaîne comme celle-ci:

../Test?/sample*.txt

alors quelle est la manière généralement acceptée d'obtenir une liste de fichiers qui correspondent à ce modèle? (par exemple, il doit correspondre ../Test1/sample22b.txtet ../Test4/sample-spiffy.txtmais pas ../Test3/sample2.blahou ../Test44/sample2.txt)

J'ai jeté un coup d'œil org.apache.commons.io.filefilter.WildcardFileFilteret cela semble être la bonne bête, mais je ne sais pas comment l'utiliser pour rechercher des fichiers dans un chemin de répertoire relatif.

Je suppose que je peux chercher la source de fourmi car elle utilise une syntaxe générique, mais je dois manquer quelque chose d'assez évident ici.

( modifier : l'exemple ci-dessus n'était qu'un exemple de cas. Je cherche le moyen d'analyser les chemins généraux contenant des caractères génériques au moment de l'exécution. J'ai trouvé comment le faire en me basant sur la suggestion de mmyers mais c'est assez ennuyeux. Sans parler de cela le JRE java semble analyser automatiquement de simples jokers dans les arguments principaux (String []) à partir d'un seul argument pour me "gagner" du temps et des tracas ... Je suis juste content de ne pas avoir d'arguments non-fichier dans le mélanger.)

Jason S
la source
2
C'est le shell qui analyse les caractères génériques, pas Java. Vous pouvez les échapper, mais le format exact dépend de votre système.
Michael Myers
2
Non ce n'est pas. Windows n'analyse pas les caractères génériques *. J'ai vérifié cela en exécutant la même syntaxe sur un fichier batch factice et en imprimant l'argument n ° 1 qui était Test / *. Obj pointant vers un répertoire plein de fichiers .obj. Il imprime "Test / *. Obj". Java semble faire quelque chose de bizarre ici.
Jason S
Huh, tu as raison; presque toutes les commandes shell intégrées développent les caractères génériques, mais pas le shell lui-même. Quoi qu'il en soit, vous pouvez simplement mettre l'argument entre guillemets pour empêcher Java d'analyser les caractères génériques: java MyClass "Test / *. Obj"
Michael Myers
3
Plus de 6 ans plus tard, pour ceux qui détestent le défilement et veulent la solution Java> = 7 zero-dep, voir et voter pour la réponse ci-dessous par @Vadzim, ou verbosly pore / bore sur docs.oracle.com/javase/tutorial/essential/io /find.html
earcam

Réponses:

81

Considérez DirectoryScanner d'Apache Ant:

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

Vous devrez référencer ant.jar (~ 1,3 Mo pour ant 1.7.1).

Misha
la source
1
excellent! btw, scanner.getIncludedDirectories () fait de même si vous avez besoin de répertoires. (getIncludedFiles ne fonctionnera pas)
Tilman Hausherr
1
Le projet wildcard sur github fonctionne également comme un charme: github.com/EsotericSoftware/wildcard
Moreaki
1
@Moreaki qui appartient à une réponse distincte, pas à un commentaire
Jason S
C'est exactement la même chose DirectoryScannerdans plexus-utils (241Kb). Ce qui est plus petit alors ant.jar(1,9 Mo).
Verhagen le
Cela marche. Mais cela semble être extrêmement lent comparé à un lsavec le même modèle de fichier (millisecondes en utilisant ls <pattern>vs minutes en utilisant le DirectoryScanner) ...
dokaspar
121

Essayez FileUtilsdepuis Apache commons-io ( listFileset iterateFilesméthodes):

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

Pour résoudre votre problème avec les TestXdossiers, je commencerais par parcourir la liste des dossiers:

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

Une solution de «force brute» mais devrait fonctionner correctement. Si cela ne répond pas à vos besoins, vous pouvez toujours utiliser le RegexFileFilter .

Vladimir
la source
2
D'accord, maintenant vous êtes arrivé exactement où était Jason S quand il a posté la question.
Michael Myers
pas assez. Il y a aussi le RegexFileFilter qui peut être utilisé (mais personnellement n'a jamais eu besoin de le faire).
Vladimir
57

Voici des exemples de liste de fichiers par modèle alimenté par Java 7 nio globbing et Java 8 lambdas:

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

ou

    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
la source
13
OuFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
amoebe
@Qstnr_La, oui, sauf les lambdas auxiliaires et les références de méthode.
Vadzim
29

Vous pouvez convertir votre chaîne générique en une expression régulière et l'utiliser avec la matchesméthode String . En suivant votre exemple:

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

Cela fonctionne pour vos exemples:

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

Et contre-exemples:

Assert.assertTrue(!"../Test3/sample2.blah".matches(regex));
Assert.assertTrue(!"../Test44/sample2.txt".matches(regex));
Fabian Steeg
la source
3
Cela ne fonctionnera pas pour les fichiers contenant des caractères regex spéciaux comme (, + ou $
djjeck
J'ai utilisé 'String regex = "^" + s.replace ("?", ".?"). Replace (" ", ". ?") + "$"' (Les astérisques ont disparu dans mon commentaire pour une raison quelconque. ..)
Jouni Aro
2
Pourquoi remplacer * par '. *? ? public static boolean isFileMatchTargetFilePattern (final File f, final String targetPattern) {`` String regex = targetPattern.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Tony
Puisque l'OP a demandé des "chemins généraux contenant des caractères génériques", vous devrez citer plus de caractères spéciaux. Je préfère utiliser 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
Addenda: "?" désigne un caractère obligatoire, il doit donc être remplacé par .au lieu de .?.
EndlosSchleife
23

Depuis Java 8, vous pouvez utiliser la Files#findméthode directement à partir de java.nio.file.

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

Exemple d'utilisation

Files.find(startingPath,
           Integer.MAX_VALUE,
           (path, basicFileAttributes) -> path.toFile().getName().matches(".*.pom")
);
Grzegorz Gajos
la source
1
Pouvez-vous étendre l'exemple pour dire imprimer le chemin de la première correspondance conservée dans le flux?
jxramos
18

Cela ne vous aidera peut-être pas pour le moment, mais JDK 7 est destiné à faire correspondre les noms de fichiers glob et regex dans le cadre de "Plus de fonctionnalités NIO".

Tom Hawtin - Tacle
la source
3
Dans Java 7: Files.newDirectoryStream (chemin, glob-pattern)
Pat Niemeyer
13

La bibliothèque de caractères génériques effectue efficacement la correspondance des noms de fichiers glob et regex:

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

L'implémentation est succincte - JAR ne fait que 12,9 kilo-octets.

NateS
la source
2
Le seul inconvénient est que ce n'est pas à Maven Central
yegor256
3
C'est OSS, allez-y et mettez-le sur Maven Central. :)
NateS
10

Un moyen simple sans utiliser d'importation externe est d'utiliser cette méthode

J'ai créé des fichiers csv nommés avec billing_201208.csv, billing_201209.csv, billing_201210.csv et cela semble fonctionner correctement.

La sortie sera la suivante si les fichiers répertoriés ci-dessus existent

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

    // Utiliser Import -> import java.io.File
        public static void main (String [] args) {
        Chaîne pathToScan = ".";
        String target_file; // fileThatYouWantToFilter
        File folderToScan = nouveau fichier (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
la source
6

Comme indiqué dans une autre réponse, la bibliothèque de caractères génériques fonctionne à la fois pour la correspondance des noms de fichiers glob et regex: http://code.google.com/p/wildcard/

J'ai utilisé le code suivant pour faire correspondre les modèles globaux, y compris absolus et relatifs sur les systèmes de fichiers de style * 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();

J'ai passé du temps à essayer d'obtenir les méthodes FileUtils.listFiles dans la bibliothèque Apache commons io (voir la réponse de Vladimir) pour le faire, mais je n'ai pas réussi (je me rends compte maintenant / je pense qu'il ne peut gérer que le modèle correspondant à un répertoire ou un fichier à la fois) .

De plus, l'utilisation de filtres regex (voir la réponse de Fabian) pour traiter des modèles de glob de type absolu arbitraires fournis par l'utilisateur sans rechercher dans tout le système de fichiers nécessiterait un prétraitement du glob fourni pour déterminer le plus grand préfixe non-regex / glob.

Bien sûr, Java 7 peut bien gérer les fonctionnalités demandées, mais malheureusement, je suis bloqué avec Java 6 pour le moment. La bibliothèque est relativement minuscule avec une taille de 13,5 Ko.

Note aux réviseurs: j'ai tenté d'ajouter ce qui précède à la réponse existante mentionnant cette bibliothèque mais la modification a été rejetée. Je n'ai pas assez de représentants pour ajouter cela en commentaire non plus. N'y a-t-il pas une meilleure façon ...

Oliver Coleman
la source
Envisagez-vous de migrer votre projet ailleurs? Voir code.google.com/p/support/wiki/ReadOnlyTransition
Luc M
1
Ce n'est pas mon projet, et il semble qu'il a déjà été migré: github.com/EsotericSoftware/wildcard
Oliver Coleman
5

Vous devriez pouvoir utiliser le WildcardFileFilter. Utilisez simplement System.getProperty("user.dir")pour obtenir le répertoire de travail. Essaye ça:

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

Vous ne devriez pas avoir besoin de remplacer *par [.*], en supposant que les filtres génériques soient utilisés java.regex.Pattern. Je n'ai pas testé cela, mais j'utilise constamment des modèles et des filtres de fichiers.

Anonyme
la source
3

Le filtre Apache est conçu pour itérer des fichiers dans un répertoire connu. Pour autoriser également les caractères génériques dans le répertoire, vous devrez diviser le chemin sur « \» ou « /» et effectuer un filtre sur chaque partie séparément.

Michael Myers
la source
1
Cela a fonctionné. C'était un peu ennuyeux, mais pas particulièrement sujet aux problèmes. Cependant, j'attends avec impatience les fonctionnalités de JDK7 pour la correspondance globale.
Jason S
0

Pourquoi ne pas utiliser quelque chose comme:

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

// now you have a fully qualified path

Ensuite, vous n'aurez pas à vous soucier des chemins relatifs et pourrez effectuer votre joker si nécessaire.

Élie
la source
1
Parce que le chemin relatif peut également avoir des caractères génériques.
Jason S
0

Méthode 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);

    }

Test jUnit:

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

Production:

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
la source
vous ne pouvez pas simplement utiliser la recherche de texte avec les chemins du système de fichiers; sinon foo/bar.txtcorrespond foo?bar.txtet ce n'est pas correct
Jason S
Jason J'ai utilisé file.getName () qui ne contient pas de chemin.
Tony
alors cela ne fonctionne pas pour l'exemple de modèle que j'ai donné:../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
la source