How to find files that match a wildcard string in Java?
Asked Answered
L

17

191

This should be really simple. If I have a String like this:

../Test?/sample*.txt

then what is a generally-accepted way to get a list of files that match this pattern? (e.g. it should match ../Test1/sample22b.txt and ../Test4/sample-spiffy.txt but not ../Test3/sample2.blah or ../Test44/sample2.txt)

I've taken a look at org.apache.commons.io.filefilter.WildcardFileFilter and it seems like the right beast but I'm not sure how to use it for finding files in a relative directory path.

I suppose I can look the source for ant since it uses wildcard syntax, but I must be missing something pretty obvious here.

(edit: the above example was just a sample case. I'm looking for the way to parse general paths containing wildcards at runtime. I figured out how to do it based on mmyers' suggestion but it's kind of annoying. Not to mention that the java JRE seems to auto-parse simple wildcards in the main(String[] arguments) from a single argument to "save" me time and hassle... I'm just glad I didn't have non-file arguments in the mix.)

Limonene answered 27/4, 2009 at 16:59 Comment(4)
That's the shell parsing the wildcards, not Java. You can escape them, but the exact format depends on your system.Perryperryman
No it's not. Windows doesn't parse * wildcards. I've checked this by running the same syntax on a dummy batchfile and printing out argument #1 which was Test/*.obj pointing to a directory full of .obj files. It prints out "Test/*.obj". Java seems to do something weird here.Limonene
Huh, you're right; almost all builtin shell commands expand wildcards, but the shell itself doesn't. Anyway, you can just put the argument in quotes to keep Java from parsing wildcards: java MyClass "Test/*.obj"Perryperryman
6+ years later, for those who loathe scrolling and want the Java >=7 zero-dep solution, see and upvote answer below by @Vadzim, or verbosely pore/bore over docs.oracle.com/javase/tutorial/essential/io/find.htmlTouchdown
E
93

Consider DirectoryScanner from Apache Ant:

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

You'll need to reference ant.jar (~ 1.3 MB for ant 1.7.1).

Eldenelder answered 30/4, 2009 at 5:55 Comment(5)
excellent! btw, scanner.getIncludedDirectories() does the same if you need directories. (getIncludedFiles won't work)Acidity
The wildcard project on github works like a charm as well: github.com/EsotericSoftware/wildcardTravesty
@Moreaki that belongs as a separate answer, not a commentLimonene
This exact same DirectoryScanner is found in plexus-utils (241Kb). Which is smaller then ant.jar (1.9Mb).Lowe
This works. But it seems to be extremely slow compared to an ls with the same file pattern (milliseconds using ls <pattern> vs. minutes when using the DirectoryScanner)...Twostep
W
149

Try FileUtils from Apache commons-io (listFiles and iterateFiles methods):

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

To solve your issue with the TestX folders, I would first iterate through the list of folders:

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

Quite a 'brute force' solution but should work fine. If this doesn't fit your needs, you can always use the RegexFileFilter.

Waadt answered 27/4, 2009 at 19:13 Comment(2)
Okay, now you've gotten to exactly where Jason S was when he posted the question.Perryperryman
not quite. There's also the RegexFileFilter that can be used (but personally never had the need to do so).Waadt
E
93

Consider DirectoryScanner from Apache Ant:

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

You'll need to reference ant.jar (~ 1.3 MB for ant 1.7.1).

Eldenelder answered 30/4, 2009 at 5:55 Comment(5)
excellent! btw, scanner.getIncludedDirectories() does the same if you need directories. (getIncludedFiles won't work)Acidity
The wildcard project on github works like a charm as well: github.com/EsotericSoftware/wildcardTravesty
@Moreaki that belongs as a separate answer, not a commentLimonene
This exact same DirectoryScanner is found in plexus-utils (241Kb). Which is smaller then ant.jar (1.9Mb).Lowe
This works. But it seems to be extremely slow compared to an ls with the same file pattern (milliseconds using ls <pattern> vs. minutes when using the DirectoryScanner)...Twostep
N
74

Here are examples of listing files by pattern powered by Java 7 nio globbing and Java 8 lambdas:

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

or

    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));
    }
Nodule answered 28/7, 2015 at 19:39 Comment(2)
Or Files.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);Zingale
I tried it on windows and it returns directorys matching the pattern, too.Meant
Y
39

Since Java 8 you can use Files#find method directly from java.nio.file.

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

Example usage

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

Or an example of putting items in a simple string collection:

import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.stream.Stream;

        final Collection<String> simpleStringCollection = new ArrayList<>();
        
        String wildCardValue = "*.txt";

        final Path dir = Paths.get(".");

        try {
            Stream<Path> results = Files.find(dir,
                    Integer.MAX_VALUE,
                    (path, basicFileAttributes) -> path.toFile().getName().matches(wildCardValue)
            );

            results.forEach(p -> simpleStringCollection.add(p.toString()));
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
Yahwistic answered 29/6, 2016 at 14:23 Comment(2)
Can you extend the example to say print the path of the first match kept in the Stream?Loosing
This is the easiest solution in 2022 IMHO. No external lib required, and can code any condition via lambda method. Thanks!Unipersonal
Q
30

You could convert your wildcard string to a regular expression and use that with String's matches method. Following your example:

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

This works for your examples:

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

And counter-examples:

Assert.assertTrue(!"../Test3/sample2.blah".matches(regex));
Assert.assertTrue(!"../Test44/sample2.txt".matches(regex));
Quickstep answered 27/4, 2009 at 19:4 Comment(5)
This will not work for files that contain special regex characters like (, + or $Pibroch
I used 'String regex = "^" + s.replace("?", ".?").replace("", ".?") + "$"' (The asterisks disappeared in my comment for some reason...)Reid
Why replace * with '.*?? public static boolean isFileMatchTargetFilePattern(final File f, final String targetPattern) {` ` String regex = targetPattern.replace(".", "\\.");` regex = regex.replace("?", ".?").replace("*", ".*"); return f.getName().matches(regex); }Unstep
Since the OP asked for "general paths containing wildcards", you would have to quote more special characters. I'd rather use 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);Blackamoor
Addendum: "?" denotes a mandatory char, so it should be replaced with . instead of .?.Blackamoor
A
18

Might not help you right now, but JDK 7 is intended to have glob and regex file name matching as part of "More NIO Features".

Adaadabel answered 27/4, 2009 at 17:25 Comment(1)
In Java 7: Files.newDirectoryStream( path, glob-pattern )Hyson
H
13

The wildcard library efficiently does both glob and regex filename matching:

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

The implementation is succinct -- JAR is only 12.9 kilobytes.

Historical answered 16/12, 2010 at 1:42 Comment(2)
The only disadvantage is that it's not in Maven CentralConeflower
It's OSS, go ahead and put it on Maven Central. :)Historical
J
12

Simple Way without using any external import is to use this method

I created csv files named with billing_201208.csv ,billing_201209.csv ,billing_201210.csv and it looks like working fine.

Output will be the following if files listed above exists

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

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

Jolandajolanta answered 5/11, 2012 at 7:45 Comment(0)
P
6

As posted in another answer, the wildcard library works for both glob and regex filename matching: http://code.google.com/p/wildcard/

I used the following code to match glob patterns including absolute and relative on *nix style file systems:

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

I spent some time trying to get the FileUtils.listFiles methods in the Apache commons io library (see Vladimir's answer) to do this but had no success (I realise now/think it can only handle pattern matching one directory or file at a time).

Additionally, using regex filters (see Fabian's answer) for processing arbitrary user supplied absolute type glob patterns without searching the entire file system would require some preprocessing of the supplied glob to determine the largest non-regex/glob prefix.

Of course, Java 7 may handle the requested functionality nicely, but unfortunately I'm stuck with Java 6 for now. The library is relatively minuscule at 13.5kb in size.

Note to the reviewers: I attempted to add the above to the existing answer mentioning this library but the edit was rejected. I don't have enough rep to add this as a comment either. Isn't there a better way...

Patmore answered 1/2, 2013 at 0:11 Comment(3)
Do you plan to migrate your project somewhere else ? See code.google.com/p/support/wiki/ReadOnlyTransitionAttending
'tis not my project, and it looks like it's been migrated already: github.com/EsotericSoftware/wildcardPatmore
use * or something, this is not wild card basedIberia
T
6

Glob of Java7: Finding Files. (Sample)

Theretofore answered 3/6, 2013 at 13:2 Comment(0)
O
5

You should be able to use the WildcardFileFilter. Just use System.getProperty("user.dir") to get the working directory. Try this:

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

You should not need to replace * with [.*], assuming wildcard filter uses java.regex.Pattern. I have not tested this, but I do use patterns and file filters constantly.

Once answered 7/7, 2011 at 23:55 Comment(0)
P
3

The Apache filter is built for iterating files in a known directory. To allow wildcards in the directory also, you would have to split the path on '\' or '/' and do a filter on each part separately.

Perryperryman answered 27/4, 2009 at 17:2 Comment(1)
This worked. It was a bit annoying, but not particularly trouble-prone. However, I do look forward to JDK7's features for glob matching.Limonene
G
1

Using Java streams only

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);
Grower answered 5/4, 2019 at 21:12 Comment(0)
F
0

Why not use do something like:

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

// now you have a fully qualified path

Then you won't have to worry about relative paths and can do your wildcarding as needed.

Fortuitous answered 27/4, 2009 at 17:7 Comment(1)
Because the relative path can have wildcards as well.Limonene
O
0

Implement the JDK FileVisitor interface. Here is an example http://wilddiary.com/list-files-matching-a-naming-pattern-java/

Oneupmanship answered 16/12, 2014 at 15:36 Comment(0)
U
0

Util Method:

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:

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

Output:

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
Unstep answered 23/1, 2016 at 19:37 Comment(3)
you can't just use text searching with filesystem paths; otherwise foo/bar.txt matches foo?bar.txt and that's not correctLimonene
Jason I used file.getName() which does not contain path.Unstep
then it doesn't work for the example pattern I gave: ../Test?/sample*.txtLimonene
B
-1

The most simple and easy way by using the io library's File class would be :

    String startingdir="The directory name";
    String filenameprefix="The file pattern"
    File startingDirFile=new File(startingdir); 
    final File[] listFiles=startingDirFile.listFiles(new FilenameFilter() {
        public boolean accept(File arg0,String arg1)
        {System.out.println(arg0+arg1);
            return arg1.matches(filenameprefix);}
        });
    System.out.println(Arrays.toString(listFiles));
Bravo answered 18/5, 2021 at 19:22 Comment(1)
It's not clear your answer is any simpler or easier than other answers already provided and accepted. And "extraordinary claims require extraordinary evidence". Why is this the most simple and easy way? You could get away with saying a simple and easy way, but should still provide a test case showing your code working.Pyrogallate

© 2022 - 2024 — McMap. All rights reserved.