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


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


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 .


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

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

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

1
Você pode estender o exemplo para dizer imprimir o caminho da primeira correspondência mantida no Stream?
Jxramos # 21/18


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.


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


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 ...


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.



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.


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.


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

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);
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.