Filtrar arquivos em Ruby: um guia prático
Filtrar arquivos com eficiência é essencial em tudo, do processamento de logs ao gerenciamento de mídia. O Ruby oferece ferramentas robustas de seleção de arquivos que vão além da simples correspondência de padrões. Vamos explorar técnicas práticas que escalam de cenários básicos a avançados.
Requisitos
Os exemplos abaixo foram testados com Ruby 3.4.10 e mime-types 3.6.0, usando o registro mime-types-data 3.2024.0806. Use uma versão do Ruby com manutenção ativa em projetos novos. Só o exemplo de consulta MIME precisa da gem mime-types. Em um projeto Bundler existente, adicione:
# Add to your Gemfile
gem 'mime-types', '3.6.0'
Execute bundle install e depois rode seus scripts com bundle exec ruby script.rb. Fora do Bundler,
instale a mesma versão diretamente:
gem install mime-types -v 3.6.0
Salve os exemplos em Ruby em um arquivo .rb e execute-o com ruby script.rb. Substitua os
diretórios de exemplo pelos seus próprios caminhos. Os resultados são arrays ou enumeradores; use
p para inspecioná-los, por exemplo, p recent_images depois do exemplo completo de
FileFilter.
A gem mime-types usa um versionamento semântico modificado para acompanhar tanto as mudanças na API quanto as atualizações dos dados do registro. Para mais detalhes, consulte a documentação do mime-types.
Métodos básicos de filtragem
A biblioteca padrão do Ruby oferece várias soluções imediatas para filtrar arquivos:
# Filter by extension
docs_dir = '/docs'
pdf_files = Dir.children(docs_dir).select do |f|
File.file?(File.join(docs_dir, f)) && File.extname(f) == '.pdf'
end
# Filter by size (1MB threshold)
large_files = Dir.glob('*').select do |f|
begin
File.file?(f) && File.size(f) > 1_000_000
rescue Errno::ENOENT, Errno::EACCES => e
warn "Error accessing #{f}: #{e.message}"
false
end
end
# Filter by modification time (last 24 hours)
recent_files = Dir.glob('*').select do |f|
begin
File.file?(f) && File.mtime(f) > (Time.now - 86400)
rescue Errno::ENOENT, Errno::EACCES => e
warn "Error accessing #{f}: #{e.message}"
false
end
end
Esses métodos usam os utilitários da classe File para verificações rápidas, sem carregar o
conteúdo dos arquivos. A verificação de extensão retorna nomes relativos a docs_dir e diferencia
maiúsculas de minúsculas. Os exemplos com glob retornam caminhos relativos ao diretório de trabalho
atual e omitem arquivos ocultos (dotfiles). File.file? exclui diretórios, mas segue links
simbólicos para arquivos comuns; essas verificações não são um sandbox do sistema de arquivos.
Os filtros de tempo usam um limite inferior, então arquivos com data futura também são incluídos.
Correspondência de padrões avançada com glob
O Dir.glob do Ruby oferece correspondência de padrões no estilo UNIX com alguns recursos
específicos do Ruby:
# Match nested Markdown files
markdown_files = Dir.glob('**/*.md').select { |f| File.file?(f) }
# Match names containing a January 2024 date (not modification times)
jan_files = Dir.glob('*').grep(/(2024-01-\d{2})/).select { |f| File.file?(f) }
# Combined size and type filter
big_images = Dir.glob('*.{jpg,png}').select do |f|
begin
File.file?(f) && File.size(f) > 500_000
rescue Errno::ENOENT, Errno::EACCES => e
warn "Error accessing #{f}: #{e.message}"
false
end
end
Use o asterisco duplo (**) para percorrer diretórios recursivamente e a expansão de chaves
para várias extensões.
Consulta de tipo MIME
Use a gem mime-types para consultar o tipo de mídia associado à extensão de um nome de arquivo:
require 'mime/types'
def media_files(dir)
Dir.children(dir).select do |f|
begin
next unless File.file?(File.join(dir, f))
MIME::Types.type_for(f).any? do |mime|
mime.media_type == 'image' || mime.media_type == 'video'
end
rescue StandardError => e
warn "Error processing #{f}: #{e.message}"
false
end
end
end
# Usage:
visual_assets = media_files('/content/assets')
Essa abordagem usa um registro de extensões; ela não inspeciona o conteúdo dos arquivos nem valida
se um arquivo é seguro. Um nome de arquivo pode ter vários tipos registrados: .mp4, por
exemplo, tem correspondências tanto de aplicação quanto de vídeo no registro testado. Verificar
todas as correspondências evita descartá-lo só porque a primeira correspondência não é um tipo de
vídeo. Isso ainda não prova que o arquivo contém vídeo.
Use uma inspeção de conteúdo separada ao aceitar uploads não confiáveis.
Para mais detalhes, consulte a
documentação da gem mime-types.
Filtragem por metadados
Combine vários campos de metadados para uma seleção precisa:
def recent_documents(path)
Dir.glob('*', base: path).map { |name| File.join(path, name) }.select do |f|
begin
next unless File.file?(f)
ext = File.extname(f).downcase
size = File.size(f)
modified = File.mtime(f)
(ext == '.pdf' || ext == '.docx') &&
size.between?(10_000, 5_000_000) &&
modified > (Time.now - 7*86400)
rescue StandardError => e
warn "Error processing #{f}: #{e.message}"
false
end
end
end
Isso seleciona arquivos PDF/DOCX diretamente dentro de path, com extensões sem diferenciação
entre maiúsculas e minúsculas e tamanhos de 10.000 a 5.000.000 bytes, inclusive. Arquivos
modificados exatamente sete dias atrás são excluídos.
Passar o diretório como base: mantém caracteres como [ no nome dele fora do padrão glob.
Considerações de desempenho
Ao processar diretórios grandes:
-
Avaliação preguiçosa: adie as verificações de metadados com
lazy.Dir.globainda monta seu array de resultados.Dir.glob('**/*').lazy .select { |f| File.file?(f) && File.size(f) > 1_000_000 } .first(10) -
Saída antecipada: falhe rápido com
breakquando possívellog_dir = '/logs' Dir.children(log_dir).each do |name| f = File.join(log_dir, name) begin next unless File.file?(f) && f.end_with?('.log') break if File.size(f) > 1_000_000_000 # Stop at first huge log process_log(f) rescue StandardError => e warn "Error processing #{f}: #{e.message}" end endDefina
process_log(path)para o processamento da sua aplicação antes de executar este trecho. -
Cache de metadados: armazene os dados acessados com frequência
file_cache = {} Dir.glob('*').each do |f| begin file_cache[f] = { mtime: File.mtime(f), size: File.size(f) } rescue StandardError => e warn "Error caching #{f}: #{e.message}" end end
Exemplo pronto para produção
Este seletor reutilizável retorna um enumerador de caminhos dentro do diretório informado:
class FileFilter
def initialize(root_dir)
@root = root_dir
end
def find_files(extensions: [], min_size: 0, max_age: Float::INFINITY)
Dir.glob('**/*', base: @root).lazy.map { |name| File.join(@root, name) }.select do |path|
begin
next unless File.file?(path)
valid_extension = extensions.empty? || extensions.include?(File.extname(path))
valid_size = File.size(path) >= min_size
valid_age = (Time.now - File.mtime(path)) < max_age
valid_extension && valid_size && valid_age
rescue StandardError => e
warn "Error processing #{path}: #{e.message}"
false
end
end
end
end
# Usage:
filter = FileFilter.new('/user/uploads')
recent_images = filter.find_files(
extensions: ['.jpg', '.png'],
min_size: 100_000,
max_age: 3600 # 1 hour
).first(100)
O uso seleciona até 100 caminhos .jpg ou .png com pelo menos 100.000 bytes,
modificados há menos de uma hora. Aqui, a correspondência de extensões diferencia maiúsculas de
minúsculas. A descoberta ainda monta um array com o glob; lazy adia as verificações de
metadados, não a travessia. Arquivos ocultos são omitidos, links simbólicos para arquivos são
seguidos e diretórios acessados por links simbólicos não são percorridos recursivamente. Uma raiz
inexistente não produz correspondências. Os arquivos podem mudar depois da seleção, então trate
erros novamente ao abri-los.
Lidando com codificações de arquivos
O Ruby normalmente lida com as codificações dos nomes de arquivos automaticamente ao usar UTF-8. No entanto, se você encontrar problemas com caracteres não ASCII em nomes de arquivos, crie um valor de exibição em UTF-8 assim:
# Convert from Ruby's known source encoding and replace invalid bytes for display
utf8_labels = Dir.glob('*').map do |f|
f.encode('UTF-8', invalid: :replace, undef: :replace).scrub
end
Mantenha o caminho original para operações no sistema de arquivos. Caracteres de substituição podem
alterar um nome de arquivo; esses rótulos servem apenas para exibição. force_encoding apenas
reinterpreta os bytes existentes com outra codificação e não faz transcodificação.
Conclusão
Ao longo deste guia, exploramos técnicas práticas para filtrar arquivos em Ruby, desde simples verificações de extensão e padrões glob até consultas MIME baseadas no nome do arquivo e filtragem por metadados. Combinando esses métodos, você pode criar pipelines de processamento de arquivos eficientes e adaptados às suas necessidades. Para soluções em larga escala e prontas para produção, considere explorar a robusta API de processamento de arquivos oferecida pela Transloadit. Acesse nossa documentação para saber mais.
