Como encontrar arquivos que correspondam a uma string curinga em Java?

157

Isso deve ser realmente simples. Se eu tiver uma String como esta:

../Test?/sample*.txt

então, qual é a maneira geralmente aceita de obter uma lista de arquivos que correspondem a esse padrão? (por exemplo, deve corresponder ../Test1/sample22b.txte ../Test4/sample-spiffy.txtmas não ../Test3/sample2.blahou ../Test44/sample2.txt)

Dei uma olhada org.apache.commons.io.filefilter.WildcardFileFiltere parece a besta certa, mas não sei como usá-la para encontrar arquivos em um caminho de diretório relativo.

Suponho que posso procurar na fonte o form, pois ele usa sintaxe curinga, mas devo estar perdendo algo bastante óbvio aqui.

( edit : o exemplo acima era apenas um exemplo de caso. Estou procurando uma maneira de analisar caminhos gerais que contêm curingas em tempo de execução. Eu descobri como fazê-lo com base na sugestão de mmyers, mas é meio irritante. Sem mencionar que o JRE java parece analisar automaticamente curingas simples nos argumentos principais (String []) de um único argumento para "economizar" tempo e aborrecimentos ... Estou feliz por não ter argumentos que não sejam de arquivo misturar.)

Jason S
fonte
2
Esse é o shell que analisa os curingas, não o Java. Você pode escapar deles, mas o formato exato depende do seu sistema.
Michael Myers
2
Não, não é. O Windows não analisa * curingas. Eu verifiquei isso executando a mesma sintaxe em um arquivo de lote fictício e imprimindo o argumento nº 1, que era Test / *. Obj, apontando para um diretório cheio de arquivos .obj. Ele imprime "Teste / *. Obj". Java parece fazer algo estranho aqui.
21730 Jason S
Huh, você está certo; quase todos os comandos internos do shell expandem curingas, mas o próprio shell não. De qualquer forma, você pode simplesmente colocar o argumento entre aspas para impedir que o Java analise curingas: java MyClass "Test / *. Obj"
Michael Myers
3
Mais de 6 anos depois, para aqueles que detestam a rolagem e desejam a solução Java> = 7 zero-dep, consulte e responda upvote abaixo por @Vadzim, ou poros / aberturas detalhadas
earcam

Respostas:

81

Considere o DirectoryScanner do Apache Ant:

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

Você precisará fazer referência ao ant.jar (~ 1,3 MB para o ant 1.7.1).

Misha
fonte
1
excelente! btw, scanner.getIncludedDirectories () faz o mesmo se você precisar de diretórios. (getIncludedFiles não funciona)
Tilman Hausherr
1
O projeto curinga no github funciona como um encanto, bem como: github.com/EsotericSoftware/wildcard
Moreaki
1
@Moreaki que pertence a uma resposta em separado, não um comentário
Jason S
Exatamente o mesmo DirectoryScanneré encontrado no plexus-utils (241Kb). Qual é menor então ant.jar(1.9Mb).
Verhagen
Isso funciona. Mas parece ser extremamente lento em comparação com um lscom o mesmo padrão de arquivo (milissegundos usando ls <pattern>minutos vs. ao usar o DirectoryScanner) ...
dokaspar
121

Tente FileUtilsno Apache commons-io ( listFilese iterateFilesmétodos):

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

Para resolver seu problema com as TestXpastas, primeiro eu iria percorrer a lista de pastas:

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

Bastante uma solução de 'força bruta', mas deve funcionar bem. Se isso não atender às suas necessidades, você sempre poderá usar o RegexFileFilter .

Vladimir
fonte
2
Ok, agora você chegou exatamente onde Jason S estava quando ele postou a pergunta.
Michael Myers
não exatamente. Há também o RegexFileFilter que pode ser usado (mas pessoalmente nunca foi necessário).
Vladimir
57

A seguir, exemplos de listagem de arquivos por padrão com tecnologia Java 7 nio globbing e Java 8 lambdas:

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

ou

    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
fonte
13
OuFiles.walk(Paths.get("..")).filter(matcher::matches).forEach(System.out::println);
amoebe 26/05
@Qstnr_La, sim, exceto lambdas auxiliares e referências de método.
Vadzim
29

Você pode converter sua string curinga em uma expressão regular e usá-la com o matchesmétodo String . Seguindo o seu exemplo:

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

Isso funciona para seus exemplos:

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

E contra-exemplos:

Assert.assertTrue(!"../Test3/sample2.blah".matches(regex));
Assert.assertTrue(!"../Test44/sample2.txt".matches(regex));
Fabian Steeg
fonte
3
Isso não vai funcionar para arquivos que contenham caracteres regex especiais como (, + ou US $
djjeck
Eu usei 'String regex = "^" + s.replace ("?", ".?"). Replace (" ", ". ?") + "$"' (Os asteriscos desapareceram no meu comentário por algum motivo. ..)
Jouni Aro
2
Por que substituir * por '. *? ? public boolean estático isFileMatchTargetFilePattern (arquivo final f, final String targetPattern) {`` String regex = targetPattern.replace (".", "\\."); ` regex = regex.replace("?", ".?").replace("* ", ".*"); return f.getName().matches(regex); }
Tony
Como o OP solicitou "caminhos gerais contendo caracteres curinga", você teria que citar mais caracteres especiais. Eu prefiro usar 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
Termo aditivo: "?" denota um caractere obrigatório, portanto deve ser substituído por em .vez de .?.
EndlosSchleife
23

Desde o Java 8, você pode usar o Files#findmétodo diretamente de java.nio.file.

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

Exemplo de uso

Files.find(startingPath,
           Integer.MAX_VALUE,
           (path, basicFileAttributes) -> path.toFile().getName().matches(".*.pom")
);
Grzegorz Gajos
fonte
1
Você pode estender o exemplo para dizer imprimir o caminho da primeira correspondência mantida no Stream?
Jxramos # 21/18
18

Pode não ajudar você agora, mas o JDK 7 deve ter o nome de arquivo glob e regex correspondente como parte de "Mais recursos da NIO".

Tom Hawtin - linha de orientação
fonte
3
Em Java 7: Files.newDirectoryStream (caminho, glob-padrão)
Pat Niemeyer
13

A biblioteca curinga efetivamente faz a correspondência dos nomes de arquivo glob e regex:

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

A implementação é sucinta - o JAR é de apenas 12,9 kilobytes.

NateS
fonte
2
A única desvantagem é que ele não está em Maven Central
yegor256
3
É OSS, vá em frente e coloque no Maven Central. :)
NateS
10

A maneira simples, sem usar nenhuma importação externa, é usar esse método

Criei arquivos csv nomeados com billing_201208.csv, billing_201209.csv, billing_201210.csv e parece que está funcionando bem.

A saída será a seguinte se os arquivos listados acima existirem

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
        Pasta do arquivo ToScan = novo arquivo (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
fonte
6

Conforme publicado em outra resposta, a biblioteca curinga funciona para correspondência de nome de arquivo glob e regex: http://code.google.com/p/wildcard/

Usei o código a seguir para corresponder aos padrões globais, incluindo absoluto e relativo nos sistemas de arquivos no estilo * 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();

Passei algum tempo tentando obter os métodos FileUtils.listFiles na biblioteca do Apache commons io (consulte a resposta de Vladimir) para fazer isso, mas não tive sucesso (eu percebo agora / acho que ele só pode lidar com padrões que correspondem a um diretório ou arquivo por vez) .

Além disso, o uso de filtros regex (consulte a resposta de Fabian) para processar padrões glob globais do tipo arbitrário fornecidos pelo usuário sem pesquisar em todo o sistema de arquivos exigiria algum pré-processamento da glob fornecida para determinar o maior prefixo não-regex / glob.

Obviamente, o Java 7 pode lidar bem com a funcionalidade solicitada, mas infelizmente estou preso ao Java 6 por enquanto. A biblioteca é relativamente minúscula, com 13,5 kb de tamanho.

Nota para os revisores: tentei adicionar o acima à resposta existente mencionando esta biblioteca, mas a edição foi rejeitada. Também não tenho representante suficiente para adicionar isso como comentário. Não existe uma maneira melhor ...

Oliver Coleman
fonte
Você planeja migrar seu projeto para outro lugar? Consulte code.google.com/p/support/wiki/ReadOnlyTransition
Luc M
1
não é meu projeto e parece que ele já foi migrado: github.com/EsotericSoftware/wildcard
Oliver Coleman
5

Você deve poder usar o WildcardFileFilter. Basta usar System.getProperty("user.dir")para obter o diretório de trabalho. Tente o seguinte:

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

Você não deve precisar substituir *com [.*], assumindo usos do filtro curinga java.regex.Pattern. Não testei isso, mas uso padrões e filtros de arquivo constantemente.

Anônimo
fonte
3

O filtro Apache foi criado para iterar arquivos em um diretório conhecido. Para permitir curingas no diretório também, você teria que dividir o caminho em ' \' ou ' /' e fazer um filtro em cada parte separadamente.

Michael Myers
fonte
1
Isso funcionou. Foi um pouco chato, mas não particularmente propenso a problemas. No entanto, estou ansioso pelos recursos do JDK7 para correspondência de globos.
27609 Jason S
0

Por que não usar faça algo como:

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

// now you have a fully qualified path

Então você não precisará se preocupar com caminhos relativos e poderá fazer seu curinga conforme necessário.

Elijah
fonte
1
Porque o caminho relativo também pode ter curingas.
21730 Jason S
0

Método 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);

    }

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

Resultado:

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
fonte
você não pode simplesmente usar a pesquisa de texto com os caminhos do sistema de arquivos; caso contrário, foo/bar.txtcorresponde foo?bar.txte isso não é correto
Jason S
Jason Eu usei file.getName () que não contém caminho.
Tony Tony
então ele não funciona para o padrão de exemplo que eu dei:../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);
Anatoliy Shuba
fonte