Come trovare i file che corrispondono a una stringa jolly in Java?


157

Questo dovrebbe essere davvero semplice. Se ho una stringa come questa:

../Test?/sample*.txt

allora qual è un modo generalmente accettato per ottenere un elenco di file che corrispondono a questo modello? (ad es. dovrebbe corrispondere ../Test1/sample22b.txte ../Test4/sample-spiffy.txtma non ../Test3/sample2.blaho ../Test44/sample2.txt)

Ho dato un'occhiata org.apache.commons.io.filefilter.WildcardFileFiltere sembra la bestia giusta ma non sono sicuro di come usarlo per trovare i file in un relativo percorso di directory.

Suppongo di poter cercare la fonte per la formica poiché utilizza la sintassi jolly, ma qui mi manca qualcosa di abbastanza ovvio.

( modifica : l'esempio sopra era solo un esempio. Sto cercando il modo di analizzare i percorsi generali contenenti caratteri jolly in fase di esecuzione. Ho capito come farlo sulla base del suggerimento di mmyers ma è un po 'fastidioso. Per non parlare del fatto che JRE JRE sembra analizzare automaticamente i caratteri jolly semplici negli argomenti principali (String []) da un singolo argomento per "risparmiare" tempo e seccature ... Sono solo felice di non avere argomenti non file nel mescolare.)


2
Questa è la shell che analizza i caratteri jolly, non Java. Puoi sfuggirli, ma il formato esatto dipende dal tuo sistema.
Michael Myers

2
No non lo è. Windows non analizza * caratteri jolly. Ho verificato questo eseguendo la stessa sintassi su un file batch fittizio e stampando l'argomento n. 1 che era Test / *. Obj che puntava a una directory piena di file .obj. Stampa "Test / *. Obj". Java sembra fare qualcosa di strano qui.
Jason S,

Eh, hai ragione; quasi tutti i comandi incorporati della shell espandono i caratteri jolly, ma la shell stessa no. Ad ogni modo, puoi semplicemente mettere l'argomento tra virgolette per impedire a Java di analizzare i caratteri jolly: java MyClass "Test / *. Obj"
Michael Myers

3
Più di 6 anni dopo, per coloro che detestano lo scrolling e desiderano la soluzione Java> = 7 zero-dep, vedere e valutare la risposta di seguito da @Vadzim, o pori / annoiarsi verbalmente
earcam

Risposte:


81

Prendi in considerazione DirectoryScanner da Apache Ant:

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

Dovrai fare riferimento a ant.jar (~ 1.3 MB per ant 1.7.1).


1
eccellente! btw, scanner.getIncludedDirectories () fa lo stesso se hai bisogno di directory. (getIncludedFiles non funzionerà)
Tilman Hausherr,

1
Anche il progetto jolly su github funziona come un incantesimo: github.com/EsotericSoftware/wildcard
Moreaki

1
@Moreaki che appartiene come una risposta separata, non un commento
Jason S

Lo stesso DirectoryScannervale per plexus -utils (241Kb). Che è più piccolo di allora ant.jar(1,9 Mb).
Verhagen,

Questo funziona Ma sembra essere estremamente lento rispetto a uno lscon lo stesso modello di file (millisecondi usando ls <pattern>vs. minuti quando si utilizza DirectoryScanner) ...
dokaspar

121

Prova FileUtilsda Apache commons-io ( listFilese iterateFilesmetodi):

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

Per risolvere il tuo problema con le TestXcartelle, dovrei prima scorrere l'elenco delle cartelle:

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

Piuttosto una soluzione "forza bruta" ma dovrebbe funzionare bene. Se ciò non soddisfa le tue esigenze, puoi sempre utilizzare RegexFileFilter .


2
Bene, ora sei arrivato esattamente dove era Jason S quando ha pubblicato la domanda.
Michael Myers

non proprio. C'è anche il RegexFileFilter che può essere utilizzato (ma personalmente non ha mai avuto la necessità di farlo).
Vladimir,

57

Ecco alcuni esempi di elenchi di file per modello basato su Java 7 nio globbing e 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));
    }

13
OppureFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
amebe,

@Qstnr_La, sì, eccetto lambda ausiliari e riferimenti ai metodi.
Vadzim,

29

È possibile convertire la stringa di caratteri jolly in un'espressione regolare e utilizzarla con il matchesmetodo String . Seguendo il tuo esempio:

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

Questo funziona per i tuoi esempi:

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

E contro-esempi:

Assert.assertTrue(!"../Test3/sample2.blah".matches(regex));
Assert.assertTrue(!"../Test44/sample2.txt".matches(regex));

3
Questo non funzionerà per i file che contengono caratteri regex speciali come (, + o $
djjeck

Ho usato 'String regex = "^" + s.replace ("?", ".?"). Replace (" ", ". ?") + "$"' (Gli asterischi sono scomparsi nel mio commento per qualche motivo. ..)
Jouni Aro,

2
Perché sostituire * con '. *? ? pubblico statico booleano isFileMatchTargetFilePattern (Final File f, final String targetPattern) {`` String regex = targetPattern.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Tony

Poiché l'OP ha richiesto "percorsi generali contenenti caratteri jolly", è necessario citare caratteri più speciali. Preferirei usare 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 il

Addendum: "?" denota un carattere obbligatorio, quindi dovrebbe essere sostituito con .anziché .?.
EndlosSchleife

23

Da Java 8 è possibile utilizzare il Files#findmetodo direttamente da java.nio.file.

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

Esempio di utilizzo

Files.find(startingPath,
           Integer.MAX_VALUE,
           (path, basicFileAttributes) -> path.toFile().getName().matches(".*.pom")
);

1
Puoi estendere l'esempio per dire stampare il percorso della prima corrispondenza mantenuta nello Stream?
jxramos,

18

Potrebbe non esserti di aiuto in questo momento, ma JDK 7 ha lo scopo di far corrispondere il nome del file glob e regex come parte di "Altre funzionalità NIO".


3
In Java 7: Files.newDirectoryStream (path, glob-pattern)
Pat Niemeyer,

13

La libreria di caratteri jolly esegue in modo efficiente la corrispondenza sia del nome glob che regex:

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

L'implementazione è concisa: JAR è solo 12,9 kilobyte.


2
L'unico svantaggio è che non si trova a Maven Central
yegor256

3
È OSS, vai avanti e mettilo su Maven Central. :)
NateS,

10

Modo semplice senza utilizzare alcuna importazione esterna è utilizzare questo metodo

Ho creato file CSV denominati con billing_201208.csv, billing_201209.csv, billing_201210.csv e sembra funzionare bene.

L'output sarà il seguente se esistono file elencati sopra

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

    // Usa Import -> import java.io.File
        public static void main (String [] args) {
        String pathToScan = ".";
        String target_file; // fileThatYouWantToFilter
        File folderToScan = nuovo file (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); 
                }
           }
     }    
}


6

Come pubblicato in un'altra risposta, la libreria di caratteri jolly funziona sia per la corrispondenza dei nomi glob che regex: http://code.google.com/p/wildcard/

Ho usato il seguente codice per abbinare i pattern glob inclusi i file system assoluti e relativi su * nix style:

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

Ho impiegato un po 'di tempo a cercare di ottenere i metodi FileUtils.listFiles nella libreria dei comuni di Apache (vedi la risposta di Vladimir) per fare questo, ma non ho avuto successo (mi rendo conto ora / penso che possa gestire solo pattern matching per una directory o un file alla volta) .

Inoltre, l'uso di filtri regex (vedere la risposta di Fabian) per l'elaborazione di schemi glob di tipo assoluto forniti dall'utente arbitrario senza la ricerca dell'intero file system richiederebbe una preelaborazione del glob fornito per determinare il più grande prefisso non regex / glob.

Naturalmente, Java 7 può gestire bene la funzionalità richiesta, ma sfortunatamente sono bloccato con Java 6 per ora. La libreria è relativamente minuscola con dimensioni di 13,5 KB.

Nota per i revisori: ho tentato di aggiungere quanto sopra alla risposta esistente menzionando questa libreria ma la modifica è stata respinta. Non ho abbastanza rappresentante per aggiungere questo come commento. Non c'è un modo migliore ...


Pensi di migrare il tuo progetto da qualche altra parte? Vedi code.google.com/p/support/wiki/ReadOnlyTransition
Luc M

1
non è il mio progetto e sembra che sia già stato migrato: github.com/EsotericSoftware/wildcard
Oliver Coleman l'

5

Dovresti essere in grado di usare il WildcardFileFilter. Basta usare System.getProperty("user.dir")per ottenere la directory di lavoro. Prova questo:

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

Non è necessario sostituirlo *con [.*], presupponendo che il filtro con caratteri jolly sia utilizzato java.regex.Pattern. Non l'ho provato, ma uso costantemente modelli e filtri di file.



3

Il filtro Apache è creato per iterare i file in una directory nota. Per consentire anche i caratteri jolly nella directory, dovresti dividere il percorso su ' \' o ' /' e fare un filtro su ogni parte separatamente.


1
Questo ha funzionato. Era un po 'fastidioso, ma non particolarmente soggetto a problemi. Tuttavia, non vedo l'ora di vedere le funzionalità di JDK7 per la corrispondenza globale.
Jason S,

0

Perché non usare qualcosa come:

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

// now you have a fully qualified path

Quindi non dovrai preoccuparti dei percorsi relativi e puoi eseguire il jolly secondo necessità.


1
Perché anche il percorso relativo può avere caratteri jolly.
Jason S,


0

Metodo Utile:

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

Produzione:

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

non puoi semplicemente usare la ricerca di testo con i percorsi del filesystem; altrimenti foo/bar.txtcorrisponde foo?bar.txte non è corretto
Jason S

Jason Ho usato file.getName () che non contiene il percorso.
Tony,

quindi non funziona per il modello di esempio che ho dato:../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);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.