fnmatch no Python: filtre nomes de arquivos

Publicado em: 10/08/2026
Tempo de leitura: 5 minutos
Pasta e lupa representando filtros de nomes com fnmatch no Python

O módulo fnmatch da biblioteca padrão compara nomes de arquivos com padrões semelhantes aos curingas usados em shells Unix. Ele é pequeno, rápido e útil quando uma aplicação já possui uma lista de nomes e precisa selecionar itens como *.py, relatorio-202?.csv ou imagem-[0-9].png. Diferentemente de glob, o módulo não percorre diretórios. Diferentemente de re, seus padrões não são expressões regulares.

Neste guia você aprenderá a usar fnmatch(), fnmatchcase(), filter(), filterfalse() e translate(), além de entender portabilidade, desempenho, segurança e limites.

Como os curingas funcionam

Os quatro elementos principais são simples: * corresponde a qualquer quantidade de caracteres; ? corresponde a exatamente um caractere; [abc] aceita um caractere da sequência; e [!abc] rejeita os caracteres indicados.

import fnmatch

print(fnmatch.fnmatch("relatorio-2026.csv", "relatorio-*.csv"))
print(fnmatch.fnmatch("foto-7.jpg", "foto-?.jpg"))
print(fnmatch.fnmatch("log-a.txt", "log-[abc].txt"))

Para corresponder literalmente a um metacaractere, coloque-o entre colchetes. O padrão [?], por exemplo, procura um ponto de interrogação real.

Filtrar uma lista existente

fnmatch.filter() recebe um iterável de nomes e devolve somente os que correspondem ao padrão. Ele é mais direto e pode ser mais eficiente que repetir a função manualmente.

from fnmatch import filter

nomes = ["app.py", "teste.py", "README.md", "dados.csv"]
python = filter(nomes, "*.py")
print(python)

Esse padrão combina bem com os.listdir(), resultados de APIs, registros de banco, nomes dentro de ZIPs e listas recebidas de outro serviço.

Excluir correspondências com filterfalse

No Python 3.14, filterfalse() passou a oferecer o resultado inverso: devolve os nomes que não correspondem. Isso elimina compreensões repetitivas e deixa a intenção mais clara.

from fnmatch import filterfalse

arquivos = ["a.tmp", "b.txt", "c.log", "d.tmp"]
permanentes = filterfalse(arquivos, "*.tmp")
print(permanentes)

Em versões anteriores, use uma compreensão com not fnmatch.fnmatch(nome, padrao).

Maiúsculas e minúsculas

fnmatch() aplica os.path.normcase() ao nome e ao padrão. Assim, o comportamento pode variar conforme o sistema operacional. Em ambientes que normalizam maiúsculas, ARQUIVO.TXT pode corresponder a *.txt. Em outros, não.

Quando a regra precisa ser idêntica em Linux, Windows e macOS, use fnmatchcase(). Apesar do nome, ela não transforma o texto: apenas realiza a comparação sensível a maiúsculas sem normalização.

from fnmatch import fnmatchcase

permitido = fnmatchcase("Relatorio.CSV", "*.csv")
print(permitido)  # False

Separadores de diretório não são especiais

O separador / não recebe tratamento especial em fnmatch. Um asterisco pode atravessar barras presentes na string. Isso é diferente da expansão de caminhos realizada por glob, que trabalha segmento por segmento.

import fnmatch

print(fnmatch.fnmatch("dados/2026/vendas.csv", "*.csv"))  # pode ser True

Se você precisa caminhar pelo sistema de arquivos, combinar diretórios recursivamente ou respeitar segmentos, use pathlib.Path.glob() ou o módulo glob. Para processar uma lista já obtida, fnmatch é adequado.

Arquivos ocultos

Nomes iniciados por ponto não recebem regra especial. O padrão * também pode corresponder a .env ou .gitignore. Portanto, implemente explicitamente a política de arquivos ocultos.

def visivel(nome):
    return not nome.startswith(".")

selecionados = [n for n in nomes if visivel(n) and fnmatch.fnmatch(n, "*")]

fnmatch não é regex

O padrão *.txt é válido para fnmatch, mas em regex o asterisco modifica o elemento anterior. Da mesma forma, grupos, quantificadores e âncoras de regex não funcionam como esperado aqui. Escolha a linguagem de padrões conforme a necessidade.

Para regras simples de nomes, curingas são mais legíveis. Para validar estrutura complexa, capturar grupos ou impor limites detalhados, use re.

Converter para expressão regular

translate() converte um padrão de shell para uma expressão regular. Isso é útil quando você deseja compilar o resultado, combinar com outras verificações ou inspecionar a regra produzida.

import fnmatch
import re

regex = re.compile(fnmatch.translate("relatorio-*.csv"))
print(bool(regex.match("relatorio-julho.csv")))

Não edite cegamente o texto gerado. Trate-o como uma implementação da semântica do padrão, que pode evoluir entre versões.

Cache e desempenho

As funções principais mantêm um cache tipado de expressões compiladas, com tamanho máximo de 32.768 padrões. Repetir os mesmos padrões é eficiente. Porém, receber padrões sempre diferentes de usuários pode reduzir o benefício do cache e aumentar trabalho de compilação.

Em serviços públicos, limite tamanho, quantidade e origem dos padrões. Evite permitir milhões de padrões arbitrários em uma única requisição. Também filtre primeiro por critérios baratos quando a lista for muito grande.

Strings e bytes

As APIs aceitam str ou bytes codificados em ISO-8859-1, mas nome e padrão precisam ter o mesmo tipo. Misturar bytes com string gera erro. Aplicações modernas normalmente devem padronizar tudo como Unicode.

import fnmatch

print(fnmatch.fnmatch(b"dados.csv", b"*.csv"))
# fnmatch.fnmatch(b"dados.csv", "*.csv")  # TypeError

Validação e segurança

fnmatch somente compara texto. Ele não confirma que o arquivo existe, não impede path traversal, não verifica permissões e não garante que o caminho esteja dentro de uma pasta permitida. Nunca use uma correspondência positiva como única autorização para abrir ou excluir um arquivo.

Normalize e resolva o caminho com pathlib, confirme o diretório-base, rejeite componentes inesperados e aplique permissões reais do sistema. Para exclusão em lote, mostre uma prévia e registre o conjunto selecionado.

Exemplo: selecionar logs com política clara

from fnmatch import fnmatchcase
from pathlib import Path

BASE = Path("logs").resolve()
PADROES = ("app-*.log", "worker-*.log")

def selecionar():
    resultado = []
    for caminho in BASE.iterdir():
        if not caminho.is_file():
            continue
        if any(fnmatchcase(caminho.name, p) for p in PADROES):
            resultado.append(caminho)
    return resultado

O código compara apenas caminho.name, evitando que diretórios alterem a semântica. A lista de padrões é controlada pela aplicação e a existência do arquivo é verificada separadamente.

Erros frequentes

  • Usar curingas em uma lista não normalizada e esperar o mesmo resultado em todos os sistemas.
  • Confundir fnmatch com regex.
  • Esperar que * pare em barras.
  • Ignorar arquivos ocultos.
  • Tratar a correspondência como autorização de acesso.
  • Misturar str e bytes.
  • Usar fnmatch para percorrer diretórios, quando glob seria mais adequado.

Boas práticas

  • Use fnmatchcase() quando a regra precisar ser portátil.
  • Compare apenas o nome quando diretórios não fizerem parte da regra.
  • Defina explicitamente a política para arquivos ocultos.
  • Limite padrões fornecidos por usuários.
  • Valide caminhos e permissões separadamente.
  • Teste limites, Unicode, maiúsculas e nomes vazios.
  • Prefira filter() ou filterfalse() para listas inteiras.

Conteúdos relacionados

Veja também os guias sobre fileinput, linecache, shlex, filecmp e bisect.

As referências oficiais são a documentação do fnmatch e a documentação do glob.

Conclusão

fnmatch é ideal para comparar nomes com curingas simples, principalmente quando os nomes já estão em memória. A API é pequena, possui cache interno e agora inclui filtragem inversa no Python 3.14. O uso correto depende de distinguir padrões de shell, regex e expansão de caminhos, além de tratar segurança e portabilidade fora da função de correspondência.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Monitor com dados binários representando arrays numéricos compactos no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    array no Python: números compactos

    Aprenda array no Python para armazenar números compactos, manipular bytes, arquivos binários e buffers com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    Círculo cromático representando conversões RGB, HSV e HLS com colorsys no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    colorsys no Python: RGB, HSV e HLS

    Aprenda colorsys no Python para converter cores entre RGB, HSV, HLS e YIQ, gerar paletas e evitar erros com escalas

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026
    Ícone de configuração representando arquivos plist com plistlib no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    plistlib no Python: arquivos plist

    Aprenda plistlib no Python para ler e gravar arquivos plist XML e binários, validar dados e integrar configurações Apple com

    Ler mais

    Tempo de leitura: 7 minutos
    08/08/2026
    Cadeado digital representando credenciais por host com netrc no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    netrc no Python: credenciais por host

    Aprenda netrc no Python para ler credenciais por host, validar permissões, tratar erros e integrar clientes de rede com segurança.

    Ler mais

    Tempo de leitura: 7 minutos
    08/08/2026
    Mensagem digital representando codificação quoted-printable com quopri no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    quopri no Python: quoted-printable

    Aprenda quopri no Python para codificar e decodificar quoted-printable em e-mails, arquivos e integrações MIME com segurança.

    Ler mais

    Tempo de leitura: 7 minutos
    08/08/2026
    Ícone de arquivo digital representando tipos MIME com mimetypes no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    mimetypes no Python: tipos MIME

    Aprenda mimetypes no Python para identificar tipos MIME, extensões e encodings com segurança em uploads, downloads e APIs web.

    Ler mais

    Tempo de leitura: 7 minutos
    08/08/2026