Filtrer des fichiers en Ruby : guide pratique
Un filtrage efficace des fichiers est essentiel dans de nombreux cas, du traitement de logs à la gestion de médias. Ruby fournit des outils robustes de sélection de fichiers qui vont au-delà de la simple correspondance de motifs. Explorons des techniques pratiques qui s’adaptent aussi bien aux scénarios simples qu’aux plus avancés.
Prérequis
Les exemples ci-dessous sont testés avec Ruby 3.4.10 et mime-types 3.6.0, avec le registre mime-types-data 3.2024.0806. Utilisez une version de Ruby maintenue pour les nouveaux projets. Seul l’exemple de recherche MIME nécessite la gem mime-types. Dans un projet Bundler existant, ajoutez :
# Add to your Gemfile
gem 'mime-types', '3.6.0'
Exécutez bundle install, puis lancez vos scripts avec bundle exec ruby script.rb. En dehors de Bundler,
installez directement la même version :
gem install mime-types -v 3.6.0
Enregistrez les exemples Ruby dans un fichier .rb et exécutez-le avec ruby script.rb. Remplacez les
répertoires d’exemple par vos propres chemins. Les résultats sont des tableaux ou des énumérateurs ;
utilisez p pour les inspecter, par exemple
p recent_images après l’exemple FileFilter complet.
La gem mime-types utilise un versionnage sémantique modifié pour suivre à la fois les changements d’API et les mises à jour des données du registre. Pour plus de détails, consultez la documentation de mime-types.
Méthodes de filtrage de base
La bibliothèque standard de Ruby propose plusieurs solutions immédiates pour filtrer des fichiers :
# 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
Ces méthodes utilisent les utilitaires de la classe File pour des vérifications rapides sans charger
le contenu des fichiers. La vérification d’extension renvoie des noms relatifs à docs_dir et est
sensible à la casse. Les exemples glob renvoient des chemins relatifs au répertoire de travail
courant et omettent les fichiers cachés (dotfiles). File.file? exclut les répertoires mais suit les
liens symboliques vers des fichiers ordinaires ; ces vérifications ne constituent pas un bac à sable
du système de fichiers. Les filtres temporels utilisent une borne inférieure : les fichiers datés
dans le futur correspondent donc aussi.
Correspondance de motifs avancée avec glob
La méthode Dir.glob de Ruby prend en charge la correspondance de motifs de style UNIX avec quelques
améliorations propres à 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
Utilisez la double étoile (**) pour parcourir récursivement les répertoires et l’expansion
d’accolades pour cibler plusieurs extensions.
Recherche de type MIME
Utilisez la gem mime-types pour rechercher le type de média associé à l’extension d’un nom de fichier :
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')
Cette approche s’appuie sur un registre d’extensions ; elle n’inspecte pas le contenu des fichiers
et ne vérifie pas qu’un fichier est sûr. Un nom de fichier peut avoir plusieurs types enregistrés :
.mp4, par exemple, possède à la fois des correspondances application et video dans le registre
testé. Vérifier chaque correspondance évite d’écarter ce fichier simplement parce que la première
correspondance n’est pas un type vidéo. Cela ne prouve toujours pas que le fichier contient de la
vidéo. Utilisez séparément une inspection du contenu lorsque vous acceptez des téléversements non
fiables. Pour plus de détails, consultez la
documentation de la gem mime-types.
Filtrage par métadonnées
Combinez plusieurs métadonnées pour une sélection précise :
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
Ce code sélectionne les fichiers PDF/DOCX situés directement dans path, avec des extensions
insensibles à la casse et des tailles comprises entre 10 000 et 5 000 000 octets inclus. Les
fichiers modifiés il y a exactement sept jours sont exclus. Transmettre le répertoire via
base: évite que des caractères comme [ dans son nom soient interprétés dans le motif glob.
Considérations de performance
Lors du traitement de grands répertoires :
-
Évaluation paresseuse : différez les vérifications de métadonnées avec
lazy.Dir.globconstruit tout de même son tableau de résultats.Dir.glob('**/*').lazy .select { |f| File.file?(f) && File.size(f) > 1_000_000 } .first(10) -
Sortie anticipée : échouez rapidement avec
breaklorsque c’est possiblelog_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 endDéfinissez
process_log(path)pour le traitement propre à votre application avant d’exécuter cet extrait. -
Mise en cache des métadonnées : stockez les données fréquemment consultées
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
Exemple prêt pour la production
Ce sélecteur réutilisable renvoie un énumérateur de chemins situés sous le répertoire fourni :
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)
L’exemple d’utilisation sélectionne jusqu’à 100 chemins .jpg ou .png d’au moins 100 000 octets,
modifiés il y a moins d’une heure. Ici, la correspondance des extensions est sensible à la casse.
La découverte construit tout de même un tableau glob ; lazy diffère les vérifications de
métadonnées, pas le parcours. Les fichiers cachés sont omis, les liens symboliques vers des fichiers
sont suivis, et les répertoires liés symboliquement ne sont pas parcourus récursivement. Une racine
manquante ne produit aucune correspondance. Les fichiers peuvent changer après la sélection :
gérez donc à nouveau les erreurs lorsque vous les ouvrez.
Gestion des encodages de fichiers
Ruby gère généralement automatiquement l’encodage des noms de fichiers lorsque vous utilisez UTF-8. Cependant, si vous rencontrez des problèmes avec des caractères non ASCII dans les noms de fichiers, créez une valeur d’affichage UTF-8 comme suit :
# 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
Conservez le chemin d’origine pour les opérations sur le système de fichiers. Les caractères de
remplacement peuvent modifier un nom de fichier ; ces libellés servent uniquement à l’affichage.
force_encoding ne fait que réétiqueter les octets et n’effectue aucun transcodage.
Conclusion
Tout au long de ce guide, nous avons exploré des techniques pratiques pour filtrer des fichiers en Ruby, des simples vérifications d’extension et motifs glob jusqu’à la recherche MIME basée sur le nom de fichier et au filtrage par métadonnées. En combinant ces méthodes, vous pouvez construire des pipelines de traitement de fichiers efficaces et adaptés à vos besoins. Pour des solutions à grande échelle et prêtes pour la production, envisagez d’explorer l’API robuste de traitement de fichiers proposée par Transloadit. Consultez notre documentation pour en savoir plus.
