Làm cách nào để tìm các tệp khớp với chuỗi ký tự đại diện trong Java?


156

Điều này nên thực sự đơn giản. Nếu tôi có một Chuỗi như thế này:

../Test?/sample*.txt

Vậy thì cách nào được chấp nhận chung để có được danh sách các tệp khớp với mẫu này? (ví dụ: nó phải phù hợp ../Test1/sample22b.txt../Test4/sample-spiffy.txtkhông ../Test3/sample2.blahhoặc ../Test44/sample2.txt)

Tôi đã xem xét org.apache.commons.io.filefilter.WildcardFileFiltervà có vẻ như đúng là con thú nhưng tôi không chắc làm thế nào để sử dụng nó để tìm tệp trong đường dẫn thư mục tương đối.

Tôi cho rằng tôi có thể tìm nguồn cho kiến ​​vì nó sử dụng cú pháp ký tự đại diện, nhưng tôi phải thiếu một cái gì đó khá rõ ràng ở đây.

( chỉnh sửa : ví dụ trên chỉ là một trường hợp mẫu. Tôi đang tìm cách phân tích các đường dẫn chung có chứa ký tự đại diện trong thời gian chạy. Tôi đã tìm ra cách thực hiện dựa trên đề xuất của mmyer nhưng thật khó chịu. java JRE dường như tự động phân tích các ký tự đại diện đơn giản trong các đối số chính (Chuỗi []) từ một đối số duy nhất để "tiết kiệm" thời gian và rắc rối của tôi ... Tôi chỉ vui mừng vì tôi không có các đối số không phải tệp trong pha trộn.)


2
Đó là cái vỏ phân tích các ký tự đại diện, không phải Java. Bạn có thể thoát chúng, nhưng định dạng chính xác phụ thuộc vào hệ thống của bạn.
Michael Myers

2
Không, không phải vậy. Windows không phân tích cú pháp * ký tự đại diện. Tôi đã kiểm tra điều này bằng cách chạy cùng một cú pháp trên một tệp giả và in ra đối số # 1 là Test / *. Obj chỉ vào một thư mục chứa đầy các tệp .obj. Nó in ra "Test / *. Obj". Java dường như làm một cái gì đó kỳ lạ ở đây.
Jason S

Huh, bạn nói đúng; hầu như tất cả các lệnh shell dựng sẵn đều mở rộng ký tự đại diện, nhưng bản thân shell thì không. Dù sao, bạn chỉ có thể đặt đối số trong dấu ngoặc kép để ngăn Java phân tích cú pháp ký tự đại diện: java MyClass "Test / *. Obj"
Michael Myers

3
Hơn 6 năm sau, đối với những người không thích di chuyển và muốn giải pháp Java> = 7 zero-dep, hãy xem và upvote câu trả lời bên dưới của @Vadzim hoặc lỗ chân lông / lỗ khoan trên docs.oracle.com/javase/tutorial/essential/io /find.html
Earcam

Câu trả lời:


81

Hãy xem xét DirectoryScanner từ 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ạn sẽ cần tham khảo ant.jar (~ 1,3 MB cho ant 1.7.1).


1
thông minh! btw, Scanner.getIncludedDirectories () thực hiện tương tự nếu bạn cần các thư mục. (getIncludedFiles sẽ không hoạt động)
Tilman Hausherr

1
Dự án ký tự đại diện trên github cũng hoạt động như một cơ duyên: github.com/EsotericSoftware/wildcard
Moreaki

1
@Moreaki thuộc về một câu trả lời riêng biệt, không phải là một nhận xét
Jason S

Điều này chính xác tương tự DirectoryScannerđược tìm thấy trong plexus-utils (241Kb). Cái nào nhỏ hơn thì ant.jar(1.9Mb).
Verhagen

Những công việc này. Nhưng nó dường như cực kỳ chậm so với một mẫu lscó cùng tệp (mili giây sử dụng ls <pattern>so với phút khi sử dụng DirectoryScanner) ...
dokaspar

120

Thử FileUtilstừ Apache commons-io ( listFilesiterateFilesphương thức):

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

Để giải quyết vấn đề của bạn với các TestXthư mục, trước tiên tôi sẽ lặp qua danh sách các thư mục:

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

Khá là một giải pháp 'vũ phu' nhưng sẽ hoạt động tốt. Nếu điều này không phù hợp với nhu cầu của bạn, bạn luôn có thể sử dụng RegexFileFilter .


2
Được rồi, bây giờ bạn đã biết chính xác Jason S đã ở đâu khi anh ấy đăng câu hỏi.
Michael Myers

không hẳn. Ngoài ra còn có RegexFileFilter có thể được sử dụng (nhưng cá nhân tôi không bao giờ có nhu cầu làm như vậy).
Vladimir

57

Dưới đây là các ví dụ về liệt kê các tệp theo mẫu được cung cấp bởi Java 7 nio globalbing và Java 8 lambdas:

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

hoặc là

    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
HoặcFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
amoebe

@Qstnr_La, vâng, ngoại trừ lambdas và các tài liệu tham khảo phương pháp.
Vadzim

29

Bạn có thể chuyển đổi chuỗi ký tự đại diện của mình thành biểu thức chính quy và sử dụng chuỗi đó với matchesphương thức của Chuỗi . Theo ví dụ của bạn:

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

Điều này làm việc cho các ví dụ của bạn:

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

Và ví dụ ngược lại:

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

3
Điều này sẽ không hoạt động đối với các tệp chứa các ký tự regex đặc biệt như (, + hoặc $
djjeck

Tôi đã sử dụng 'String regex = "^" + s.replace ("?", ".?"). Thay thế (" ", ". ?") + "$"' (Dấu hoa thị biến mất trong nhận xét của tôi vì một số lý do. ..)
Jouni Aro

2
Tại sao thay thế * bằng '. *? ? boolean tĩnh isFileMatchTargetFilePotype (Tệp cuối f, chuỗi đích TargetPotype) {`` String regex = targetPotype.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Tony

Vì OP đã yêu cầu "các đường dẫn chung chứa ký tự đại diện", bạn sẽ phải trích dẫn các ký tự đặc biệt hơn. Tôi muốn sử dụng 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

Phụ lục: "?" biểu thị một char bắt buộc, vì vậy nó nên được thay thế bằng .thay vì .?.
EndlosSchleife

23

Vì Java 8, bạn có thể sử dụng Files#findphương thức trực tiếp từ java.nio.file.

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

Ví dụ sử dụng

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

1
Bạn có thể mở rộng ví dụ để nói in đường dẫn của trận đấu đầu tiên được giữ trong Luồng không?
jxramos

18

Có thể không giúp bạn ngay bây giờ, nhưng JDK 7 được dự định có khớp tên tệp toàn cầu và regex như một phần của "Các tính năng NIO khác".


3
Trong Java 7: Files.newDirectoryStream (đường dẫn, mô hình toàn cầu)
Pat Niemeyer

13

Thư viện ký tự đại diện có hiệu quả khớp cả tên tệp toàn cầu và regex:

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

Việc thực hiện ngắn gọn - JAR chỉ có 12,9 kilobyte.


2
Nhược điểm duy nhất là nó không ở Maven Central
yegor256

3
Đó là OSS, hãy tiếp tục và đưa nó vào Maven Central. :)
NateS

10

Cách đơn giản mà không sử dụng bất kỳ nhập khẩu bên ngoài là sử dụng phương pháp này

Tôi đã tạo các tệp csv có tên với billing_201208.csv, billing_201209.csv, billing_201210.csv và có vẻ như nó hoạt động tốt.

Đầu ra sẽ như sau nếu các tệp được liệt kê ở trên tồn tại

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

    // Sử dụng Nhập -> nhập java.io.File
        public static void main (String [] args) {
        Chuỗi pathToScan = ".";
        Chuỗi đích_file; // fileThatYouWantToFilter
        Thư mục tệp ToScan = Tệp mới (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

Như được đăng trong một câu trả lời khác, thư viện ký tự đại diện hoạt động cho cả khớp tên tệp toàn cầu và regex: http://code.google.com.vn/p/wildcard/

Tôi đã sử dụng mã sau đây để khớp với các mẫu toàn cầu bao gồm tuyệt đối và tương đối trên các hệ thống tệp kiểu * 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();

Tôi đã dành một chút thời gian để cố gắng có được các phương thức FileUtils.listFiles trong thư viện commons io của Apache (xem câu trả lời của Vladimir) để làm điều này nhưng không thành công (tôi nhận ra bây giờ / nghĩ rằng nó chỉ có thể xử lý mẫu phù hợp với một thư mục hoặc tệp tại một thời điểm) .

Ngoài ra, sử dụng các bộ lọc regex (xem câu trả lời của Fabian) để xử lý các mẫu toàn cầu loại tuyệt đối do người dùng cung cấp mà không cần tìm kiếm toàn bộ hệ thống tệp sẽ yêu cầu một số tiền xử lý của toàn cầu được cung cấp để xác định tiền tố không phải regex / global lớn nhất.

Tất nhiên, Java 7 có thể xử lý tốt chức năng được yêu cầu, nhưng tiếc là hiện tại tôi đang bị mắc kẹt với Java 6. Thư viện tương đối rất nhỏ với kích thước 13,5kb.

Lưu ý cho người đánh giá: Tôi đã cố gắng thêm câu trả lời ở trên vào câu trả lời hiện có đề cập đến thư viện này nhưng bản chỉnh sửa đã bị từ chối. Tôi cũng không có đủ đại diện để thêm nhận xét này. Không có cách nào tốt hơn ...


Bạn có kế hoạch để di chuyển dự án của bạn ở một nơi khác? Xem code.google.com/p/support/wiki/ReadOnlyTransition
Luc M

1
'đây không phải là dự án của tôi và có vẻ như nó đã được di chuyển: github.com/EsotericSoftware/wildcard
Oliver Coleman

5

Bạn sẽ có thể sử dụng WildcardFileFilter. Chỉ cần sử dụng System.getProperty("user.dir")để có được thư mục làm việc. Thử cái này:

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

Bạn không cần phải thay thế *bằng [.*], giả sử sử dụng bộ lọc ký tự đại diện java.regex.Pattern. Tôi chưa thử nghiệm điều này, nhưng tôi thường xuyên sử dụng các mẫu và bộ lọc tệp.



3

Bộ lọc Apache được xây dựng để lặp lại các tệp trong một thư mục đã biết. Để cho phép ký tự đại diện trong thư mục cũng vậy, bạn sẽ phải chia đường dẫn trên ' \' hoặc ' /' và thực hiện một bộ lọc trên từng phần riêng biệt.


1
Điều này đã làm việc. Đó là một chút khó chịu, nhưng không đặc biệt dễ gặp rắc rối. Tuy nhiên, tôi rất mong đợi các tính năng của JDK7 để phù hợp với toàn cầu.
Jason S

0

Tại sao không sử dụng làm một cái gì đó như:

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

// now you have a fully qualified path

Sau đó, bạn sẽ không phải lo lắng về các đường dẫn tương đối và có thể thực hiện ký tự đại diện khi cần thiết.


1
Bởi vì đường dẫn tương đối có thể có ký tự đại diện là tốt.
Jason S


0

Phương pháp sử dụng:

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

    }

Kiểm tra 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));
        }
    }
}

Đầu ra:

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

bạn không thể chỉ sử dụng tìm kiếm văn bản với các đường dẫn hệ thống tập tin; nếu không thì foo/bar.txtkhớp foo?bar.txtvà điều đó không đúng
Jason S

Jason Tôi đã sử dụng file.getName () không chứa đường dẫn.
Tony

sau đó nó không hoạt động cho mẫu ví dụ tôi đã đưa ra:../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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.