วิธีค้นหาไฟล์ที่ตรงกับสตริงตัวแทนใน Java?


157

มันควรจะง่ายจริงๆ ถ้าฉันมีสตริงเช่นนี้:

../Test?/sample*.txt

วิธีที่เป็นที่ยอมรับโดยทั่วไปในการรับรายการไฟล์ที่ตรงกับรูปแบบนี้คืออะไร (เช่นมันควรจะตรง../Test1/sample22b.txtและ../Test4/sample-spiffy.txtแต่ไม่../Test3/sample2.blahหรือ../Test44/sample2.txt)

ฉันดูorg.apache.commons.io.filefilter.WildcardFileFilterแล้วดูเหมือนสัตว์ร้ายที่ถูกต้อง แต่ฉันไม่แน่ใจว่าจะใช้มันเพื่อค้นหาไฟล์ในเส้นทางไดเรกทอรีแบบสัมพัทธ์ได้อย่างไร

ฉันคิดว่าฉันสามารถหาแหล่งที่มาของมดได้เนื่องจากมันใช้ไวยากรณ์ไวด์การ์ด แต่ฉันต้องหายไปบางสิ่งที่เห็นได้ชัดที่นี่

( แก้ไข : ตัวอย่างข้างต้นเป็นเพียงตัวอย่างเท่านั้นฉันกำลังมองหาวิธีการแยกวิเคราะห์เส้นทางทั่วไปที่มีอักขระตัวแทนที่รันไทม์ฉันคิดวิธีทำตามคำแนะนำของ mmyers แต่มันน่ารำคาญไม่ต้องพูดถึง java JRE ดูเหมือนว่าจะใช้สัญลักษณ์แทนอย่างง่าย ๆ ในอาร์กิวเมนต์หลัก (สตริง []) จากอาร์กิวเมนต์เดียวเพื่อ "บันทึก" ฉันเวลาและความยุ่งยาก ... ฉันแค่ดีใจที่ฉันไม่ได้มีข้อโต้แย้งที่ไม่ใช่ไฟล์ใน ผสม.)


2
นั่นคือเชลล์ที่วิเคราะห์สัญลักษณ์แทนไม่ใช่ Java คุณสามารถหลบหนีได้ แต่รูปแบบที่แน่นอนนั้นขึ้นอยู่กับระบบของคุณ
Michael Myers

2
ไม่มันไม่ใช่. Windows ไม่แยกวิเคราะห์สัญลักษณ์แทน * ฉันได้ตรวจสอบเรื่องนี้โดยใช้ไวยากรณ์เดียวกันใน dummy batchfile และพิมพ์อาร์กิวเมนต์ # 1 ซึ่งเป็น Test / *. obj ชี้ไปที่ไดเรกทอรีที่เต็มไปด้วยไฟล์. obj มันพิมพ์ "Test / *. obj" Java ดูเหมือนว่าจะทำสิ่งที่แปลกที่นี่
304 Jason S

ใช่แล้วคุณพูดถูก คำสั่ง builtin shell เกือบทั้งหมดขยาย wildcard แต่เชลล์เองไม่ได้ อย่างไรก็ตามคุณสามารถใส่อาร์กิวเมนต์ในเครื่องหมายคำพูดเพื่อป้องกัน Java จากการวิเคราะห์สัญลักษณ์: java MyClass "Test / *. obj"
Michael Myers

3
6+ ปีต่อมาสำหรับผู้ที่เกลียดการเลื่อนและต้องการ Java> = 7 ศูนย์แก้ปัญหาดูและ upvote คำตอบด้านล่างโดย @Vadzim หรือรูขุมขน verbosely / เบื่อมากกว่าdocs.oracle.com/javase/tutorial/essential/io /find.html
earcam

คำตอบ:


81

พิจารณา DirectoryScanner จาก Apache Ant:

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

คุณจะต้องอ้างอิง ant.jar (~ 1.3 MB สำหรับ ant 1.7.1)


1
! ที่ดีเยี่ยม btw, scanner.getIncludedDirectories () ทำเช่นเดียวกันหากคุณต้องการไดเรกทอรี (getIncludedFiles จะไม่ทำงาน)
Tilman Hausherr

1
โครงการตัวแทนใน github ทำงานได้อย่างมีเสน่ห์เช่นกัน: github.com/EsotericSoftware/wildcard
Moreaki

1
@Moreaki ที่เป็นคำตอบที่แยกต่างหากไม่แสดงความคิดเห็น
Jason S

นี้แน่นอนเดียวกันDirectoryScannerที่พบในช่องท้อง-utils (241Kb) ซึ่งมีขนาดเล็กกว่านั้นant.jar(1.9Mb)
Verhagen

วิธีนี้ใช้ได้ผล แต่ดูเหมือนว่าจะช้ามากเมื่อเทียบlsกับรูปแบบไฟล์เดียวกัน (มิลลิวินาทีใช้ls <pattern>เทียบกับนาทีเมื่อใช้ DirectoryScanner) ...
dokaspar

121

ลองFileUtilsจากApache Commons-io ( listFilesและiterateFilesวิธีการ):

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

เพื่อแก้ปัญหาของคุณกับTestXโฟลเดอร์ฉันจะทำซ้ำผ่านรายการโฟลเดอร์:

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

วิธีการแก้ปัญหาค่อนข้าง 'กำลังดุร้าย' แต่ควรทำงานได้ดี หากสิ่งนี้ไม่ตรงกับความต้องการของคุณคุณสามารถใช้RegexFileFilter ได้ตลอดเวลา


2
โอเคตอนนี้คุณได้รู้ว่า Jason S อยู่ที่ไหนเมื่อเขาโพสต์คำถาม
Michael Myers

ไม่มาก นอกจากนี้ยังมี RegexFileFilter ที่สามารถใช้งานได้ (แต่โดยส่วนตัวไม่จำเป็นต้องทำเช่นนั้น)
วลาดิมีร์

57

นี่คือตัวอย่างของการแสดงรายการไฟล์ตามรูปแบบที่ขับเคลื่อนโดยJava 7 nio globbingและ Java 8 lambdas:

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

หรือ

    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
หรือFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
อะมีบา

@Qstnr_La ใช่ยกเว้น lambdas แนะแนวและการอ้างอิงวิธีการ
Vadzim

29

คุณสามารถแปลงสตริงไวด์การ์ดของคุณเป็นนิพจน์ทั่วไปและใช้กับmatchesวิธีการของสตริง ทำตามตัวอย่างของคุณ:

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

ใช้งานได้กับตัวอย่างของคุณ:

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

และตัวอย่างเคาน์เตอร์:

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

3
สิ่งนี้จะใช้ไม่ได้กับไฟล์ที่มีอักขระพิเศษ regex เช่น (, + หรือ $
djjeck

ฉันใช้ 'String regex = "^" + s.replace ("?", ".?"). แทนที่ (" ", ". ?") + "$"' (เครื่องหมายดอกจันหายไปในความคิดเห็นของฉันด้วยเหตุผลบางประการ .. )
Jouni Aro

2
ทำไมแทนที่ * ด้วย '. *? ? บูลีนสาธารณะแบบคงที่ isFileMatchTargetFilePattern (ไฟล์สุดท้าย f, สุดท้ายสตริง targetPattern) {`` String regex = targetPattern.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Tony

เนื่องจาก OP ขอให้ "เส้นทางทั่วไปที่มีอักขระตัวแทน" คุณจะต้องอ้างอิงอักขระพิเศษเพิ่มเติม ฉันอยากใช้ 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

ภาคผนวก: "?" หมายถึงถ่านบังคับดังนั้นจึงควรถูกแทนที่ด้วยแทน. .?
EndlosSchleife

23

ตั้งแต่ Java 8 คุณสามารถใช้วิธีการโดยตรงจากFiles#findjava.nio.file

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

ตัวอย่างการใช้งาน

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

1
คุณสามารถขยายตัวอย่างเพื่อบอกว่าพิมพ์เส้นทางของการแข่งขันนัดแรกที่เก็บไว้ในสตรีมได้หรือไม่?
jxramos


13

ไลบรารี wildcard ทำการจับคู่ชื่อไฟล์ glob และ regex อย่างมีประสิทธิภาพ:

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

การติดตั้งใช้งานง่าย - JAR มีขนาดเพียง 12.9 กิโลไบท์เท่านั้น


2
ข้อเสียอย่างเดียวคือมันไม่ได้อยู่ใน Maven Central
yegor256

3
มันคือ OSS ไปข้างหน้าและวางไว้บน Maven Central :)
NateS

10

วิธีง่าย ๆ โดยไม่ใช้การอิมพอร์ตภายนอกคือการใช้วิธีนี้

ฉันสร้างไฟล์ csv ชื่อด้วย billing_201208.csv, billing_201209.csv, billing_201210.csv และดูเหมือนว่าจะทำงานได้ดี

เอาต์พุตจะเป็นดังต่อไปนี้หากไฟล์ที่แสดงอยู่ด้านบนมี

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

    // ใช้นำเข้า -> นำเข้า java.io.File
        โมฆะคงที่สาธารณะหลัก (String [] args) {
        String pathToScan = ".";
        String target_file; // fileThatYouWantToFilter
        ไฟล์ folderToScan = ไฟล์ใหม่ (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

ดังที่โพสต์ในคำตอบอื่นไลบรารี wildcard ใช้ได้กับทั้งชื่อไฟล์ glob และ regex ที่ตรงกับ: http://code.google.com/p/wildcard/

ฉันใช้รหัสต่อไปนี้เพื่อจับคู่รูปแบบ glob รวมถึงระบบไฟล์สไตล์สมบูรณ์แบบและสัมพัทธ์กับ *:

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

ฉันใช้เวลาพยายามหาวิธี FileUtils.listFiles ใน Apache commons io library (ดูคำตอบของ Vladimir) เพื่อทำสิ่งนี้ แต่ไม่ประสบความสำเร็จ (ฉันรู้แล้วตอนนี้ / คิดว่ามันสามารถจัดการรูปแบบการจับคู่ไดเรกทอรีหรือไฟล์ในเวลาเดียวเท่านั้น) .

นอกจากนี้การใช้ตัวกรอง regex (ดูคำตอบของเฟเบียน) สำหรับการประมวลผลรูปแบบ glob ประเภทผู้ใช้โดยพลการโดยไม่ต้องค้นหาระบบไฟล์ทั้งหมดจะต้องมีการประมวลผลล่วงหน้าของ glob ที่ให้มาเพื่อกำหนดคำนำหน้า non-regex / glob ที่ใหญ่ที่สุด

แน่นอนว่า Java 7 อาจรองรับฟังก์ชั่นที่ร้องขอได้อย่างน่าเสียดาย แต่ตอนนี้ฉันติดกับ Java 6 แล้ว ไลบรารีมีขนาดเล็กเพียง 13.5kb

หมายเหตุถึงผู้ตรวจทาน: ฉันพยายามที่จะเพิ่มข้างต้นในคำตอบที่มีอยู่พูดถึงห้องสมุดนี้ แต่การแก้ไขถูกปฏิเสธ ฉันไม่มีตัวแทนเพียงพอที่จะเพิ่มสิ่งนี้เป็นความคิดเห็น ไม่มีทางที่ดีกว่านี้ ...


คุณวางแผนที่จะโยกย้ายโครงการของคุณที่อื่นหรือไม่? ดูcode.google.com/p/support/wiki/ReadOnlyTransition
Luc M

1
ไม่ใช่โครงการของฉันและดูเหมือนว่ามีการย้ายข้อมูลไปแล้ว: github.com/EsotericSoftware/wildcard
Oliver Coleman

5

WildcardFileFilterคุณควรจะสามารถที่จะใช้ เพียงใช้System.getProperty("user.dir")เพื่อรับไดเรกทอรีทำงาน ลองสิ่งนี้:

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

คุณไม่ควรจะต้องแทนที่*ด้วยสมมติว่าใช้สัญลักษณ์แทนตัวกรอง[.*] java.regex.Patternฉันยังไม่ได้ทดสอบสิ่งนี้ แต่ฉันใช้รูปแบบและตัวกรองไฟล์อย่างต่อเนื่อง



3

ตัวกรอง Apache สร้างขึ้นเพื่อทำซ้ำไฟล์ในไดเรกทอรีที่รู้จัก หากต้องการอนุญาตให้ใช้สัญลักษณ์ตัวแทนในไดเรกทอรีคุณจะต้องแยกเส้นทางใน ' \' หรือ ' /' และทำตัวกรองในแต่ละส่วนแยกกัน


1
สิ่งนี้ใช้ได้ผล มันน่ารำคาญนิดหน่อย แต่ก็ไม่ได้ทำให้เกิดปัญหาได้ง่ายนัก อย่างไรก็ตามฉันหวังว่าจะได้คุณสมบัติของ JDK7 สำหรับการจับคู่แบบกลม
Jason S

0

ทำไมไม่ใช้ทำสิ่งที่ชอบ:

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

// now you have a fully qualified path

จากนั้นคุณไม่ต้องกังวลเกี่ยวกับเส้นทางที่สัมพันธ์กันและสามารถใช้สัญลักษณ์แทนได้ตามต้องการ


1
เนื่องจากเส้นทางสัมพัทธ์สามารถมีอักขระตัวแทนได้เช่นกัน
46499 Jason S


0

วิธีการใช้ประโยชน์:

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

    }

การทดสอบ 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));
        }
    }
}

เอาท์พุท:

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

คุณไม่สามารถใช้การค้นหาข้อความด้วยเส้นทางของระบบไฟล์ มิฉะนั้นfoo/bar.txtตรงfoo?bar.txtและที่ไม่ถูกต้อง
เจสัน S

Jason ฉันใช้ file.getName () ซึ่งไม่มีเส้นทาง
โทนี่

แล้วมันไม่ทำงานสำหรับรูปแบบตัวอย่างที่ฉันให้:../Test?/sample*.txt
เจสัน 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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.