Bagaimana menemukan file yang cocok dengan string wildcard di Java?

157

Ini harus sangat sederhana. Jika saya memiliki String seperti ini:

../Test?/sample*.txt

lalu apa cara yang diterima secara umum untuk mendapatkan daftar file yang cocok dengan pola ini? (misalnya itu harus cocok ../Test1/sample22b.txtdan ../Test4/sample-spiffy.txttetapi tidak ../Test3/sample2.blahatau ../Test44/sample2.txt)

Saya telah melihat org.apache.commons.io.filefilter.WildcardFileFilterdan sepertinya binatang yang tepat tetapi saya tidak yakin bagaimana menggunakannya untuk menemukan file di jalur direktori relatif.

Saya kira saya bisa mencari sumber semut karena menggunakan sintaks wildcard, tetapi saya harus kehilangan sesuatu yang cukup jelas di sini.

( sunting : contoh di atas hanyalah contoh kasus. Saya sedang mencari cara untuk mem-parsing jalur umum yang berisi wildcard saat runtime. Saya menemukan cara melakukannya berdasarkan saran mmyers tetapi agak menyebalkan. Belum lagi bahwa java JRE tampaknya mem-parsing wildcard sederhana di argumen utama (String []) dari satu argumen untuk "menghemat" waktu dan kerumitan saya ... Saya hanya senang saya tidak memiliki argumen non-file di campuran.)

Jason S
sumber
2
Itu shell mengurai wildcard, bukan Java. Anda dapat melarikan diri dari mereka, tetapi format yang tepat tergantung pada sistem Anda.
Michael Myers
2
Bukan itu. Windows tidak mem-parsing * wildcard. Saya telah memeriksa ini dengan menjalankan sintaks yang sama pada batchfile dummy dan mencetak argumen # 1 yang merupakan Test / *. Obj yang menunjuk ke direktori yang penuh dengan file .obj. Mencetak "Test / *. Obj". Java tampaknya melakukan sesuatu yang aneh di sini.
Jason S
Hah, kamu benar; hampir semua perintah shell builtin memperluas wildcard, tetapi shell itu sendiri tidak. Pokoknya, Anda hanya dapat menempatkan argumen dalam tanda kutip untuk menjaga Java dari parsing wildcard: java MyClass "Uji / * obj"
Michael Myers
3
6+ tahun kemudian, bagi mereka yang tidak suka menggulir dan menginginkan Java> = 7 solusi nol-dep, lihat dan hapus jawaban di bawah ini dengan @Vadzim, atau secara perlahan-lahan buka / bore atas docs.oracle.com/javase/tutorial/essential/io /find.html
earcam

Jawaban:

81

Pertimbangkan DirectoryScanner dari Apache Ant:

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

Anda harus merujuk ant.jar (~ 1.3 MB untuk ant 1.7.1).

Misha
sumber
1
luar biasa! btw, scanner.getIncludedDirectories () melakukan hal yang sama jika Anda memerlukan direktori. (getIncludedFiles tidak akan berfungsi)
Tilman Hausherr
1
Proyek wildcard di github juga berfungsi seperti pesona: github.com/EsotericSoftware/wildcard
Moreaki
1
@Moreaki yang dimiliki sebagai jawaban terpisah, bukan komentar
Jason S
Ini sama persis DirectoryScannerditemukan di pleksus-utils (241Kb). Yang lebih kecil ant.jar(1.9Mb).
Verhagen
Ini bekerja. Tetapi tampaknya sangat lambat dibandingkan dengan lsdengan pola file yang sama (milidetik menggunakan ls <pattern>vs menit saat menggunakan DirectoryScanner) ...
dokaspar
121

Mencoba FileUtils dari Apache commons-io ( listFilesdan iterateFilesmetode):

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

Untuk mengatasi masalah Anda dengan TestXfolder, pertama-tama saya akan mengulangi daftar folder:

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

Solusi yang cukup 'brute force' tetapi harus bekerja dengan baik. Jika ini tidak sesuai dengan kebutuhan Anda, Anda selalu dapat menggunakan RegexFileFilter .

Vladimir
sumber
2
Oke, sekarang Anda sudah sampai di tempat Jason S berada ketika dia memposting pertanyaan.
Michael Myers
tidak terlalu. Ada juga RegexFileFilter yang dapat digunakan (tetapi secara pribadi tidak pernah perlu melakukannya).
Vladimir
57

Berikut adalah contoh daftar file berdasarkan pola yang didukung oleh Java 7 nio globbing dan Java 8 lambdas:

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

atau

    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
sumber
13
AtauFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
amoebe
@Qstnr_La, ya, kecuali lambdas auxilar dan referensi metode.
Vadzim
29

Anda bisa mengonversi string wildcard Anda menjadi ekspresi reguler dan menggunakannya dengan matchesmetode String . Mengikuti contoh Anda:

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

Ini berfungsi untuk contoh Anda:

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

Dan contoh tandingan:

Assert.assertTrue(!"../Test3/sample2.blah".matches(regex));
Assert.assertTrue(!"../Test44/sample2.txt".matches(regex));
Fabian Steeg
sumber
3
Ini tidak akan berfungsi untuk file yang berisi karakter regex khusus seperti (, + atau $
djjeck
Saya menggunakan 'String regex = "^" + s.replace ("?", ".?"). Replace (" ", ". ?") + "$"' (Tanda bintang menghilang di komentar saya karena alasan tertentu. ..)
Jouni Aro
2
Mengapa ganti * dengan '. *? ? boolean statis publik isFileMatchTargetFilePattern (File terakhir f, final String targetPattern) {`` String regex = targetPattern.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Tony
Karena OP meminta "jalur umum yang berisi wildcard", Anda harus mengutip karakter yang lebih khusus. Saya lebih suka menggunakan 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
Tambahan: "?" menunjukkan char yang wajib, jadi harus diganti dengan .bukan .?.
EndlosSchleife
23

Karena Java 8 Anda dapat menggunakan Files#findmetode langsung dari java.nio.file.

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

Contoh penggunaan

Files.find(startingPath,
           Integer.MAX_VALUE,
           (path, basicFileAttributes) -> path.toFile().getName().matches(".*.pom")
);
Grzegorz Gajos
sumber
1
Bisakah Anda memperluas contoh dengan mengatakan mencetak jalur pertandingan pertama yang disimpan dalam Stream?
jxramos
18

Mungkin tidak membantu Anda sekarang, tetapi JDK 7 dimaksudkan untuk memiliki pencocokan nama file glob dan regex sebagai bagian dari "Lebih Banyak Fitur NIO".

Tom Hawtin - tackline
sumber
3
Di Jawa 7: Files.newDirectoryStream (path, glob-pattern)
Pat Niemeyer
13

Perpustakaan wildcard efisien melakukan pencocokan glob dan regex nama file:

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

Implementasinya singkat - JAR hanya 12,9 kilobyte.

NateS
sumber
2
Satu-satunya kelemahan adalah bahwa itu tidak di Maven Central
yegor256
3
Ini OSS, silakan dan letakkan di Maven Central. :)
NateS
10

Cara sederhana tanpa menggunakan impor eksternal adalah dengan menggunakan metode ini

Saya membuat file csv dengan nama billing_201208.csv, billing_201209.csv, billing_201210.csv dan sepertinya berfungsi dengan baik.

Output akan menjadi berikut jika file yang tercantum di atas ada

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

    // Gunakan Impor -> impor java.io.File
        public static public void (String [] args) {
        String pathToScan = ".";
        String target_file; // fileThatYouWantToFilter
        File folderToScan = File baru (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
sumber
6

Seperti yang diposting pada jawaban lain, pustaka wildcard berfungsi untuk pencocokan nama file glob dan regex: http://code.google.com/p/wildcard/

Saya menggunakan kode berikut untuk mencocokkan pola glob termasuk absolut dan relatif pada sistem file gaya * 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();

Saya menghabiskan beberapa waktu mencoba untuk mendapatkan metode FileUtils.listFiles di perpustakaan Apache commons io (lihat jawaban Vladimir) untuk melakukan ini tetapi tidak berhasil (saya sadar sekarang / saya pikir itu hanya dapat menangani pencocokan pola satu direktori atau file pada suatu waktu) .

Selain itu, menggunakan filter regex (lihat jawaban Fabian) untuk memproses pengguna sewenang-wenang memberikan pola glob tipe absolut tanpa mencari seluruh sistem file akan membutuhkan beberapa preprocessing glob yang disediakan untuk menentukan awalan non-regex / glob terbesar.

Tentu saja, Java 7 dapat menangani fungsionalitas yang diminta dengan baik, tetapi sayangnya saya terjebak dengan Java 6 untuk saat ini. Perpustakaan relatif sangat kecil pada ukuran 13.5kb.

Catatan untuk pengulas: Saya berusaha menambahkan jawaban di atas ke jawaban yang ada yang menyebutkan perpustakaan ini tetapi hasil edit ditolak. Saya tidak punya cukup tenaga untuk menambahkan ini sebagai komentar. Apakah tidak ada cara yang lebih baik ...

Oliver Coleman
sumber
Apakah Anda berencana untuk memigrasi proyek Anda di tempat lain? Lihat code.google.com/p/support/wiki/ReadOnlyTransition
Luc M
1
Ini bukan proyek saya, dan sepertinya sudah dimigrasikan: github.com/EsotericSoftware/wildcard
Oliver Coleman
5

Anda harus dapat menggunakan WildcardFileFilter. Cukup gunakan System.getProperty("user.dir")untuk mendapatkan direktori kerja. Coba ini:

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

Anda tidak perlu menggantinya *dengan [.*]asumsi menggunakan wildcard filter java.regex.Pattern. Saya belum menguji ini, tapi saya menggunakan pola dan filter file terus-menerus.

Anonim
sumber
3

Filter Apache dibuat untuk melakukan iterasi file dalam direktori yang dikenal. Untuk mengizinkan wildcard dalam direktori juga, Anda harus membagi path pada ' \' atau ' /' dan melakukan filter pada setiap bagian secara terpisah.

Michael Myers
sumber
1
Ini berhasil. Agak menyebalkan, tapi tidak terlalu rawan masalah. Namun, saya sangat menantikan fitur JDK7 untuk pencocokan glob.
Jason S
0

Mengapa tidak menggunakan, lakukan sesuatu seperti:

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

// now you have a fully qualified path

Maka Anda tidak perlu khawatir tentang jalur relatif dan dapat melakukan wildcarding sesuai kebutuhan.

Elia
sumber
1
Karena jalur relatif dapat memiliki wildcard juga.
Jason S
0

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

    }

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

Keluaran:

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
sumber
Anda tidak bisa hanya menggunakan pencarian teks dengan jalur sistem file; jika tidak foo/bar.txtcocok foo?bar.txtdan itu tidak benar
Jason S
Jason saya menggunakan file.getName () yang tidak mengandung path.
Tony
maka itu tidak bekerja untuk pola contoh yang saya berikan:../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);
Shuba Anatoliy
sumber