Как найти файлы, которые соответствуют подстановочной строке в Java?

157

Это должно быть действительно просто. Если у меня есть такая строка:

../Test?/sample*.txt

тогда каков общепринятый способ получения списка файлов, соответствующих этому шаблону? (например , он должен соответствовать ../Test1/sample22b.txtи , ../Test4/sample-spiffy.txtно не ../Test3/sample2.blahили ../Test44/sample2.txt)

Я взглянул на него, org.apache.commons.io.filefilter.WildcardFileFilterи это похоже на правильного зверя, но я не уверен, как использовать его для поиска файлов в относительном пути к каталогу.

Я полагаю, что могу найти источник для ant, так как он использует подстановочный синтаксис, но я должен упустить что-то довольно очевидное здесь.

( edit : приведенный выше пример был всего лишь примером. Я ищу способ разбирать общие пути, содержащие подстановочные знаки во время выполнения. Я выяснил, как это сделать, основываясь на предложении mmyers, но это немного раздражает. Не говоря уже о том, что JRE Java, кажется, автоматически разбирает простые подстановочные знаки в основном (аргументы String []) из одного аргумента, чтобы «сэкономить» мое время и хлопоты ... Я просто рад, что у меня не было файловых аргументов в смешивание.)

Джейсон С
источник
2
Это оболочка, разбирающая символы подстановки, а не Java. Вы можете избежать их, но точный формат зависит от вашей системы.
Майкл Майерс
2
Нет, это не так. Windows не разбирает * подстановочные знаки. Я проверил это, запустив тот же синтаксис для фиктивного пакетного файла и распечатав аргумент # 1, который был Test / *. Obj, указывающий на каталог, полный файлов .obj. Распечатывает «Test / *. Obj». Java, кажется, делает что-то странное здесь.
Джейсон С
Да, ты прав; почти все встроенные команды оболочки расширяют символы подстановки, но сама оболочка этого не делает. В любом случае, вы можете просто поместить аргумент в кавычки, чтобы Java не разбирал символы подстановки: java MyClass "Test / *. Obj"
Майкл Майерс
3
Спустя 6 с лишним лет для тех, кто ненавидит прокрутку и хочет получить решение с нулевой разверткой Java> = 7, см. Ответ @vadzim ниже и подробно расскажите о 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 МБ для муравья 1.7.1).

Миша
источник
1
превосходно! Кстати, scanner.getIncludedDirectories () делает то же самое, если вам нужны каталоги. (getIncludedFiles не будет работать)
Тилман Хаушерр
1
Проект с подстановочными знаками на github также работает как шарм: github.com/EsotericSoftware/wildcard
Moreaki
1
@Moreaki, который принадлежит как отдельный ответ, а не комментарий
Jason S
Точно так же DirectoryScannerможно найти в сплетении утилит (241Kb). Что меньше, чем ant.jar(1,9 МБ).
Верхаген
Это работает. Но это кажется очень медленным по сравнению 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
Хорошо, теперь вы попали именно туда, где был Джейсон С., когда он отправил вопрос.
Майкл Майерс
не совсем. Есть также 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));
    }
Vadzim
источник
13
ИлиFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
амеба
@Qstnr_La, да, кроме вспомогательных лямбд и ссылок на методы.
Вадим
29

Вы можете преобразовать свою подстановочную строку в регулярное выражение и использовать его с matchesметодом String . Следуя вашему примеру:

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
Это не будет работать для файлов, которые содержат специальные символы регулярного выражения, такие как (, + или $
djjeck
Я использовал 'String regex = "^" + s.replace ("?", ".?"). Replace (" ", ". ?") + "$"' (Звездочки в моем комментарии почему-то исчезли. ..)
Джуни Аро
2
Почему заменить * на '. *? ? public static boolean isFileMatchTargetFilePattern (конечный файл f, конечный String targetPattern) {`` String regex = targetPattern.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Тони
Поскольку ОП запрашивал «общие пути, содержащие подстановочные знаки», вам придется заключать в кавычки больше специальных символов. Я бы предпочел использовать 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#findметод непосредственно из java.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
18

Возможно, это не поможет вам прямо сейчас, но JDK 7 предназначен для соответствия имени файла glob и regex как части «Больше возможностей NIO».

Том Хотин - Tackline
источник
3
В Java 7: Files.newDirectoryStream (путь, шаблон глобуса)
Пэт Нимейер
13

Библиотека подстановочных знаков эффективно выполняет сопоставление имен файлов как 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

    // Используем Import -> import java.io.File
        public static void main (String [] args) {
        String pathToScan = ".";
        String target_file; // fileThatYouWantToFilter
        File 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

Как указано в другом ответе, библиотека подстановочных знаков работает как с глобальными, так и с регулярными выражениями: http://code.google.com/p/wildcard/

Я использовал следующий код для сопоставления шаблонов glob, включая абсолютные и относительные в файловых системах стиля * 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();

Я потратил некоторое время, пытаясь получить методы FileUtils.listFiles в библиотеке Apache commons io (см. Ответ Владимира), но безуспешно (сейчас я понимаю / думаю, что он может обрабатывать только шаблоны, соответствующие одному каталогу или файлу за раз) ,

Кроме того, использование фильтров регулярных выражений (см. Ответ Фабиана) для обработки произвольных предоставленных пользователем шаблонов глобуса абсолютного типа без поиска во всей файловой системе потребует некоторой предварительной обработки поставляемого глобуса для определения наибольшего префикса без регулярного выражения / глобуса.

Конечно, Java 7 может хорошо обрабатывать запрошенную функциональность, но, к сожалению, я пока застрял с Java 6. Библиотека относительно небольшая, ее размер составляет 13,5 КБ.

Примечание для рецензентов: я попытался добавить вышеприведенный ответ к существующему ответу с упоминанием этой библиотеки, но редактирование было отклонено. У меня недостаточно представителей, чтобы добавить это в качестве комментария. Нет ли лучшего способа ...

Оливер Коулман
источник
Планируете ли вы перенести свой проект в другое место? См. Code.google.com/p/support/wiki/ReadOnlyTransition
Люк М
1
это не мой проект, и похоже, что он уже перенесен: github.com/EsotericSoftware/wildcard
Оливер Коулман
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 для сопоставления глобусов.
Джейсон С
0

Почему бы не использовать сделать что-то вроде:

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

// now you have a fully qualified path

Тогда вам не нужно будет беспокоиться об относительных путях, и вы сможете выполнять подстановочные знаки по мере необходимости.

Илия
источник
1
Потому что относительный путь может иметь подстановочные знаки.
Джейсон С
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и это не правильно
Джейсон С.
Джейсон Я использовал file.getName (), который не содержит пути.
Тони
тогда это не работает для примера, который я привел:../Test?/sample*.txt
Джейсон С
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);
Анатолий Шуба
источник