Wie finde ich Dateien, die mit einer Platzhalterzeichenfolge in Java übereinstimmen?


156

Das sollte wirklich einfach sein. Wenn ich einen String wie diesen habe:

../Test?/sample*.txt

Was ist dann ein allgemein akzeptierter Weg, um eine Liste von Dateien zu erhalten, die diesem Muster entsprechen? (zB sollte es passen ../Test1/sample22b.txtund ../Test4/sample-spiffy.txtaber nicht ../Test3/sample2.blahoder ../Test44/sample2.txt)

Ich habe es mir angesehen org.apache.commons.io.filefilter.WildcardFileFilterund es scheint das richtige Biest zu sein, aber ich bin mir nicht sicher, wie ich es verwenden soll, um Dateien in einem relativen Verzeichnispfad zu finden.

Ich nehme an, ich kann die Quelle nach Ameise durchsuchen, da sie Wildcard-Syntax verwendet, aber mir muss hier etwas ziemlich Offensichtliches fehlen.

( Bearbeiten : Das obige Beispiel war nur ein Beispielfall. Ich suche nach einer Möglichkeit, allgemeine Pfade mit Platzhaltern zur Laufzeit zu analysieren. Ich habe anhand des Vorschlags von mmyers herausgefunden, wie das geht, aber es ist irgendwie ärgerlich. Ganz zu schweigen davon Die Java-JRE scheint einfache Platzhalter in den Hauptargumenten (String []) automatisch aus einem einzigen Argument zu analysieren, um mir Zeit und Ärger zu "sparen". Ich bin nur froh, dass ich keine Argumente außerhalb der Datei in der mischen.)


2
Das ist die Shell, die die Platzhalter analysiert, nicht Java. Sie können ihnen entkommen, aber das genaue Format hängt von Ihrem System ab.
Michael Myers

2
Nein, ist es nicht. Windows analysiert keine * Platzhalter. Ich habe dies überprüft, indem ich dieselbe Syntax für eine Dummy-Batchdatei ausgeführt und das Argument Nr. 1 ausgedruckt habe, das Test / *. Obj war und auf ein Verzeichnis voller OBJ-Dateien verweist. Es druckt "Test / *. Obj" aus. Java scheint hier etwas Seltsames zu tun.
Jason S

Huh, du hast recht; Fast alle integrierten Shell-Befehle erweitern Platzhalter, die Shell selbst jedoch nicht. Wie auch immer, Sie können das Argument einfach in Anführungszeichen setzen, um zu verhindern, dass Java Platzhalter analysiert: Java MyClass "Test / *. Obj"
Michael Myers

3
6+ Jahre später, für diejenigen, die das Scrollen verabscheuen und die Java> = 7 Zero-Dep-Lösung wollen, siehe und stimmen Sie die Antwort unten von @Vadzim ab oder stöbern Sie ausführlich über docs.oracle.com/javase/tutorial/essential/io /find.html
earcam

Antworten:


81

Betrachten Sie DirectoryScanner von Apache Ant:

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

Sie müssen auf ant.jar verweisen (~ 1,3 MB für ant 1.7.1).


1
Ausgezeichnet! Übrigens macht scanner.getIncludedDirectories () dasselbe, wenn Sie Verzeichnisse benötigen. (getIncludedFiles funktioniert nicht)
Tilman Hausherr

1
Das Wildcard-Projekt auf Github funktioniert auch wie ein Zauber: github.com/EsotericSoftware/wildcard
Moreaki

1
@ Moreaki, die als separate Antwort gehört, kein Kommentar
Jason S

Genau das gleiche DirectoryScannerfindet sich in Plexus-Utils (241Kb). Welches ist kleiner als ant.jar(1,9 MB).
Verhagen

Das funktioniert. Aber es scheint extrem langsam zu sein im Vergleich zu einem lsmit dem gleichen ls <pattern>
Dateimuster

120

Versuchen FileUtilsvon Apache commons-io ( listFilesund iterateFilesMethoden):

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

Um Ihr Problem mit den TestXOrdnern zu lösen , würde ich zuerst die Liste der Ordner durchlaufen:

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

Eine ziemliche Brute-Force-Lösung, sollte aber gut funktionieren. Wenn dies nicht Ihren Anforderungen entspricht, können Sie jederzeit den RegexFileFilter verwenden .


2
Okay, jetzt bist du genau dort angekommen, wo Jason S war, als er die Frage gestellt hat.
Michael Myers

nicht ganz. Es gibt auch den RegexFileFilter, der verwendet werden kann (aber persönlich nie nötig war).
Vladimir

57

Hier sind einige Beispiele für das Auflisten von Dateien durch Muster bereitgestellt von Java 7 nio Globbing und Java 8 lambdas:

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

oder

    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
OderFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
Amöbe

@Qstnr_La, ja, mit Ausnahme von zusätzlichen Lambdas und Methodenreferenzen.
Vadzim

29

Sie können Ihre Platzhalterzeichenfolge in einen regulären Ausdruck konvertieren und diesen mit der matchesMethode von String verwenden. Folgen Sie Ihrem Beispiel:

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

Dies funktioniert für Ihre Beispiele:

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

Und Gegenbeispiele:

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

3
Dies funktioniert nicht für Dateien, die spezielle Regex-Zeichen wie (, + oder $
djjeck

Ich habe 'String regex = "^" + s.replace ("?", ".?"). Replace (" ", ". ?") + "$"' (Die Sternchen sind aus irgendeinem Grund in meinem Kommentar verschwunden. ..)
Jouni Aro

2
Warum * durch '. * Ersetzen? ? public static boolean isFileMatchTargetFilePattern (endgültige Datei f, endgültiger String targetPattern) {`` String regex = targetPattern.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Tony

Da das OP nach "allgemeinen Pfaden mit Platzhaltern" fragte, müssten Sie mehr Sonderzeichen angeben. Ich würde lieber Pattern.quote verwenden: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

Nachtrag: "?" bezeichnet ein obligatorisches Zeichen, daher sollte es durch .anstelle von ersetzt werden .?.
EndlosSchleife

23

Seit Java 8 können Sie die Files#findMethode direkt von verwenden java.nio.file.

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

Anwendungsbeispiel

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

1
Können Sie das Beispiel dahingehend erweitern, dass Sie den Pfad der ersten Übereinstimmung drucken, die im Stream gespeichert ist?
Jxramos

18

Könnte Ihnen momentan nicht helfen, aber JDK 7 soll im Rahmen von "Weitere NIO-Funktionen" den Namen der Glob- und Regex-Dateinamen abgleichen.


3
In Java 7: Files.newDirectoryStream (Pfad, Glob-Muster)
Pat Niemeyer

13

Die Platzhalterbibliothek führt effizient den Abgleich von Glob- und Regex-Dateinamen durch:

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

Die Implementierung ist prägnant - JAR beträgt nur 12,9 Kilobyte.


2
Der einzige Nachteil ist, dass es nicht in Maven Central ist
yegor256

3
Es ist OSS, machen Sie weiter und setzen Sie es auf Maven Central. :)
NateS

10

Ein einfacher Weg ohne externen Import ist die Verwendung dieser Methode

Ich habe CSV-Dateien mit den Namen billing_201208.csv, billing_201209.csv, billing_201210.csv erstellt und es sieht so aus, als würde es gut funktionieren.

Die Ausgabe erfolgt wie folgt, wenn die oben aufgeführten Dateien vorhanden sind

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

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

Wie in einer anderen Antwort angegeben, funktioniert die Platzhalterbibliothek sowohl für die Übereinstimmung von Glob- als auch Regex-Dateinamen: http://code.google.com/p/wildcard/

Ich habe den folgenden Code verwendet, um Glob-Muster einschließlich absoluter und relativer Dateisysteme im * nix-Stil abzugleichen:

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

Ich habe einige Zeit damit verbracht, die FileUtils.listFiles-Methoden in der Apache Commons io-Bibliothek abzurufen (siehe Vladimir's Antwort), um dies zu tun, hatte aber keinen Erfolg (ich weiß jetzt / denke, dass es nur Muster verarbeiten kann, die jeweils einem Verzeichnis oder einer Datei entsprechen). .

Darüber hinaus würde die Verwendung von Regex-Filtern (siehe Fabians Antwort) zur Verarbeitung beliebiger vom Benutzer angegebener Glob-Muster vom absoluten Typ ohne Durchsuchen des gesamten Dateisystems eine Vorverarbeitung des bereitgestellten Glob erfordern, um das größte Nicht-Regex / Glob-Präfix zu bestimmen.

Natürlich kann Java 7 die angeforderte Funktionalität gut handhaben, aber leider bin ich vorerst mit Java 6 festgefahren. Die Bibliothek ist mit 13,5 KB relativ klein.

Hinweis für die Prüfer: Ich habe versucht, das oben Gesagte zu der vorhandenen Antwort hinzuzufügen, in der diese Bibliothek erwähnt wird, aber die Bearbeitung wurde abgelehnt. Ich habe auch nicht genug Repräsentanten, um dies als Kommentar hinzuzufügen. Gibt es keinen besseren Weg ...


Planen Sie, Ihr Projekt an einen anderen Ort zu migrieren? Siehe code.google.com/p/support/wiki/ReadOnlyTransition
Luc M

1
Es ist nicht mein Projekt, und es sieht so aus, als ob es bereits migriert wurde: github.com/EsotericSoftware/wildcard
Oliver Coleman

5

Sie sollten in der Lage sein, die zu verwenden WildcardFileFilter. Verwenden Sie einfach System.getProperty("user.dir"), um das Arbeitsverzeichnis zu erhalten. Versuche dies:

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

Sie sollten nicht ersetzen müssen *mit der [.*]Annahme , Wildcard - Filter verwendet java.regex.Pattern. Ich habe dies nicht getestet, aber ich verwende ständig Muster und Dateifilter.



3

Der Apache-Filter dient zum Iterieren von Dateien in einem bekannten Verzeichnis. Um Platzhalter auch im Verzeichnis zuzulassen, müssten Sie den Pfad auf ' \' oder ' /' aufteilen und für jedes Teil einen Filter separat ausführen.


1
Das hat funktioniert. Es war ein bisschen nervig, aber nicht besonders störanfällig. Ich freue mich jedoch auf die Funktionen von JDK7 für den Glob-Abgleich.
Jason S

0

Warum nicht so etwas tun wie:

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

// now you have a fully qualified path

Dann müssen Sie sich keine Gedanken mehr über relative Pfade machen und können Ihre Platzhalter nach Bedarf ausführen.


1
Weil der relative Pfad auch Platzhalter haben kann.
Jason S


0

Util-Methode:

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

    }

Einheitentest:

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

Ausgabe:

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

Sie können die Textsuche nicht einfach mit Dateisystempfaden verwenden. ansonsten foo/bar.txtpasst foo?bar.txtund das ist nicht richtig
Jason S

Jason Ich habe file.getName () verwendet, das keinen Pfad enthält.
Tony

dann funktioniert es nicht für das Beispielmuster, das ich gegeben habe:../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);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.