Jak znaleźć pliki, które pasują do ciągu znaków wieloznacznych w Javie?

157

To powinno być naprawdę proste. Jeśli mam taki ciąg:

../Test?/sample*.txt

w takim razie jaki jest ogólnie akceptowany sposób uzyskania listy plików pasujących do tego wzorca? (np powinna pasować ../Test1/sample22b.txti ../Test4/sample-spiffy.txtale nie ../Test3/sample2.blahlub ../Test44/sample2.txt)

Przyjrzałem się i wygląda na org.apache.commons.io.filefilter.WildcardFileFilterto, że jest to właściwa bestia, ale nie jestem pewien, jak go użyć do wyszukiwania plików w względnej ścieżce katalogu.

Przypuszczam, że mogę poszukać źródła mrówki, ponieważ używa ona składni symboli wieloznacznych, ale chyba brakuje mi tutaj czegoś dość oczywistego.

( edycja : powyższy przykład to tylko przykładowy przypadek. Szukam sposobu na przeanalizowanie ogólnych ścieżek zawierających symbole wieloznaczne w czasie wykonywania. Zorientowałem się, jak to zrobić na podstawie sugestii mmyersów, ale jest to trochę denerwujące. Nie wspominając o tym) JRE java wydaje się automatycznie analizować proste symbole wieloznaczne w głównym (argumenty String []) z pojedynczego argumentu, aby "zaoszczędzić" czas i kłopoty ... Cieszę się, że nie mam argumentów niebędących plikami w mieszać.)

Jason S.
źródło
2
To powłoka analizująca symbole wieloznaczne, a nie Java. Możesz im uciec, ale dokładny format zależy od twojego systemu.
Michael Myers
2
Nie, nie jest. System Windows nie analizuje * symboli wieloznacznych. Sprawdziłem to, uruchamiając tę ​​samą składnię na fałszywym pliku wsadowym i wypisując argument nr 1, którym był Test / * .obj wskazujący na katalog pełen plików .obj. Wyświetla "Test / *. Obj". Wydaje się, że Java robi tutaj coś dziwnego.
Jason S
Huh, masz rację; prawie wszystkie wbudowane polecenia powłoki rozwijają symbole wieloznaczne, ale sama powłoka nie. Tak czy inaczej, możesz po prostu umieścić argument w cudzysłowie, aby Java nie analizowała symboli wieloznacznych: java MyClass "Test / *. Obj"
Michael Myers
3
Ponad 6 lat później, dla tych, którzy nienawidzą przewijania i chcą rozwiązania Java> = 7 zero-dep, zobacz i zagłosuj poniżej odpowiedzi autorstwa @Vadzim lub werbalnie przeglądaj / nudzi na docs.oracle.com/javase/tutorial/essential/io /find.html
kamera douszna

Odpowiedzi:

81

Rozważ DirectoryScanner z Apache Ant:

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

Będziesz musiał odwołać się do ant.jar (~ 1,3 MB dla ant 1.7.1).

Misha
źródło
1
doskonały! btw, scanner.getIncludedDirectories () robi to samo, jeśli potrzebujesz katalogów. (getIncludedFiles nie będzie działać)
Tilman Hausherr
1
Projekt wieloznaczny na githubie również działa jak urok: github.com/EsotericSoftware/wildcard
Moreaki
1
@Moreaki to osobna odpowiedź, a nie komentarz
Jason S
Dokładnie DirectoryScannerto samo można znaleźć w plexus-utils (241Kb). Który jest mniejszy niż ant.jar(1,9 Mb).
Verhagen
To działa. Ale wydaje się, że jest bardzo powolny w porównaniu lsz tym samym wzorcem pliku (milisekundy w ls <pattern>porównaniu z minutami podczas korzystania z DirectoryScanner) ...
dokaspar
121

Wypróbuj FileUtilsz Apache commons-io ( listFilesi iterateFilesmetody):

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

Aby rozwiązać problem z TestXfolderami, najpierw przejrzałbym listę folderów:

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

Całkiem rozwiązanie typu „brutalna siła”, ale powinno działać dobrze. Jeśli to nie odpowiada Twoim potrzebom, zawsze możesz użyć RegexFileFilter .

Vladimir
źródło
2
Okay, teraz dotarłeś dokładnie do miejsca, w którym był Jason S, kiedy opublikował pytanie.
Michael Myers
nie do końca. Istnieje również RegexFileFilter, którego można użyć (ale osobiście nigdy nie miał takiej potrzeby).
Vladimir
57

Oto przykłady listowania plików według wzorca obsługiwanego przez globbing Java 7 nio i lambdy Java 8:

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

lub

    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
źródło
13
LubFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
ameba
@Qstnr_La, tak, z wyjątkiem pomocniczych lambd i odwołań do metod.
Wadzim
29

Możesz przekonwertować swój ciąg znaków wieloznacznych na wyrażenie regularne i użyć go z matchesmetodą String . Idąc za twoim przykładem:

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

To działa dla twoich przykładów:

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

I kontrprzykłady:

Assert.assertTrue(!"../Test3/sample2.blah".matches(regex));
Assert.assertTrue(!"../Test44/sample2.txt".matches(regex));
Fabian Steeg
źródło
3
To nie zadziała dla plików, które zawierają specjalne znaki wyrażenia regularnego, takie jak (, + lub $
djjeck
Użyłem 'String regex = "^" + s.replace ("?", ".?"). Replace (" ", ". ?") + "$"' (Gwiazdki zniknęły z mojego komentarza z jakiegoś powodu. ..)
Jouni Aro
2
Po co zastępować * '. *? ? public static boolean isFileMatchTargetFilePattern (final File f, final String targetPattern) {`` String regex = targetPattern.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Tony
Ponieważ program operacyjny poprosił o „ogólne ścieżki zawierające symbole wieloznaczne”, musiałbyś podać więcej znaków specjalnych. Wolałbym używać Pattern.cote: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
Dodatek: „?” oznacza obowiązkowy znak, dlatego należy go zastąpić .zamiast .?.
EndlosSchleife
23

Od Java 8 możesz używać Files#findmetody bezpośrednio z java.nio.file.

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

Przykładowe użycie

Files.find(startingPath,
           Integer.MAX_VALUE,
           (path, basicFileAttributes) -> path.toFile().getName().matches(".*.pom")
);
Grzegorz Gajos
źródło
1
Czy możesz rozszerzyć przykład, aby powiedzieć, że wypisz ścieżkę pierwszego dopasowania przechowywanego w strumieniu?
jxramos
18

Może ci to teraz nie pomóc, ale JDK 7 ma dopasować nazwy plików glob i wyrażeń regularnych w ramach „Więcej funkcji NIO”.

Tom Hawtin - haczyk
źródło
3
W Javie 7: Files.newDirectoryStream (ścieżka, wzorzec glob)
Pat Niemeyer
13

Biblioteka symboli wieloznacznych wydajnie dopasowuje zarówno nazwy plików glob, jak i wyrażeń regularnych:

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

Wdrożenie jest zwięzłe - JAR ma tylko 12,9 kilobajta.

NateS
źródło
2
Jedyną wadą jest to, że nie ma go w Maven Central
yegor256
3
To OSS, śmiało i umieść to na Maven Central. :)
NateS
10

Prostym sposobem bez użycia zewnętrznego importu jest użycie tej metody

Utworzyłem pliki csv o nazwach billing_201208.csv, billing_201209.csv, billing_201210.csv i wygląda na to, że działają dobrze.

Dane wyjściowe będą następujące, jeśli pliki wymienione powyżej istnieją

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

    // Użyj importu -> importuj java.io.File
        public static void main (String [] args) {
        String pathToScan = ".";
        String target_file; // fileThatYouWantToFilter
        Plik folderToScan = nowy plik (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
źródło
6

Jak napisano w innej odpowiedzi, biblioteka symboli wieloznacznych działa zarówno w przypadku dopasowywania nazw plików glob, jak i wyrażeń regularnych: http://code.google.com/p/wildcard/

Użyłem następującego kodu, aby dopasować wzorce glob, w tym bezwzględne i względne w systemach plików w stylu * 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();

Spędziłem trochę czasu próbując uzyskać metody FileUtils.listFiles w bibliotece Apache commons io (zobacz odpowiedź Vladimira), aby to zrobić, ale bez powodzenia (teraz zdaję sobie sprawę / myślę, że może obsługiwać tylko wzorce pasujące do jednego katalogu lub pliku na raz) .

Dodatkowo, użycie filtrów wyrażeń regularnych (patrz odpowiedź Fabiana) do przetwarzania dowolnych wzorców glob typu absolutnego dostarczonych przez użytkownika bez przeszukiwania całego systemu plików wymagałoby pewnego wstępnego przetworzenia dostarczonego globu w celu określenia największego prefiksu innego niż wyrażenia regularne / glob.

Oczywiście Java 7 może ładnie obsłużyć żądaną funkcjonalność, ale niestety na razie utknąłem w Javie 6. Biblioteka jest stosunkowo malutka i ma rozmiar 13,5 kb.

Uwaga dla recenzentów: próbowałem dodać powyższe do istniejącej odpowiedzi wspominającej o tej bibliotece, ale zmiana została odrzucona. Nie mam wystarczającej liczby przedstawicieli, aby dodać to jako komentarz. Czy nie ma lepszego sposobu ...

Oliver Coleman
źródło
Czy planujesz przenieść swój projekt gdzie indziej? Zobacz code.google.com/p/support/wiki/ReadOnlyTransition
Luc M
1
to nie jest mój projekt i wygląda na to, że został już zmigrowany: github.com/EsotericSoftware/wildcard
Oliver Coleman,
5

Powinieneś być w stanie użyć WildcardFileFilter. Po prostu użyj, System.getProperty("user.dir")aby uzyskać katalog roboczy. Spróbuj tego:

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

Nie powinieneś zastępować *go [.*], zakładając, że używany jest filtr wieloznaczny java.regex.Pattern. Nie testowałem tego, ale stale używam wzorców i filtrów plików.

Anonimowy
źródło
3

Filtr Apache jest zbudowany w celu iteracji plików w znanym katalogu. Aby zezwolić na używanie symboli wieloznacznych również w katalogu, musisz podzielić ścieżkę na „ \” lub „ /” i wykonać filtr dla każdej części osobno.

Michael Myers
źródło
1
To zadziałało. Było to trochę denerwujące, ale niezbyt podatne na kłopoty. Nie mogę się jednak doczekać funkcji JDK7 do dopasowywania globów.
Jason S
0

Dlaczego nie użyć, zrób coś takiego:

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

// now you have a fully qualified path

Wtedy nie będziesz musiał martwić się o ścieżki względne i w razie potrzeby możesz użyć symboli wieloznacznych.

Eliasz
źródło
1
Ponieważ ścieżka względna może również zawierać symbole wieloznaczne.
Jason S
0

Metoda użytkowa:

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

Wynik:

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
źródło
nie możesz po prostu użyć wyszukiwania tekstu ze ścieżkami systemu plików; inaczej foo/bar.txtpasuje foo?bar.txti to nie jest poprawne
Jason S
Jason użyłem file.getName (), który nie zawiera ścieżki.
Tony
to nie działa dla przykładu, który podałem:../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
źródło