2010-07-23 5 views
22

Je n'ai pas encore trouvé un bon exemple d'utilisation de php RegexIterator pour parcourir récursivement un répertoire. Le résultat final serait que je veuille spécifier un répertoire et y trouver tous les fichiers avec quelques extensions données. Dites par exemple seulement les extensions html/php. De plus, je veux filtrer les dossiers tels du type .Trash-0, .Trash-500, etc.Comment utiliser RegexIterator en PHP

<?php 
$Directory = new RecursiveDirectoryIterator("/var/www/dev/"); 
$It = new RecursiveIteratorIterator($Directory); 
$Regex = new RegexIterator($It,'/^.+\.php$/i',RecursiveRegexIterator::GET_MATCH); 

foreach($Regex as $v){ 
    echo $value."<br/>"; 
} 
?> 

Est-ce que j'ai jusqu'à présent, mais les résultats suivants: Erreur fatale: Uncaught exception 'de UnexpectedValueException' avec le message 'RecursiveDirectoryIterator :: __ construct (/media/hdmovies1/.Trash-0)

Des suggestions?

Répondre

46

Il y a plusieurs façons de faire quelque chose comme ça, je vais vous donner deux approches rapides: rapide et sale, plutôt longue et moins sale (bien que ce soit un vendredi soir donc nous ' re autorisé à aller un peu fou).

1. rapide (et sale)

Cela implique simplement écrire une expression régulière (peut être divisé en plusieurs) pour filtrer la collection de fichiers d'un seul coup rapide.

(Seules les deux lignes commentées sont vraiment importantes pour le concept.)

$directory = new RecursiveDirectoryIterator(__DIR__); 
$flattened = new RecursiveIteratorIterator($directory); 

// Make sure the path does not contain "/.Trash*" folders and ends eith a .php or .html file 
$files = new RegexIterator($flattened, '#^(?:[A-Z]:)?(?:/(?!\.Trash)[^/]+)+/[^/]+\.(?:php|html)$#Di'); 

foreach($files as $file) { 
    echo $file . PHP_EOL; 
} 

Cette approche a un certain nombre de questions, mais il est rapide à mettre en œuvre étant juste une ligne unique (bien que la regex pourrait être une peine à déchiffrer).

2. Moins rapide (et moins sale)

Une approche plus réutilisable est de créer un couple de filtres sur mesure (utilisant l'expression rationnelle, ou tout ce que vous voulez!) Pour rogner la liste des disponibles éléments dans le RecursiveDirectoryIterator initial à seulement ceux que vous voulez. Ce qui suit est seulement un exemple, écrit rapidement juste pour vous, d'étendre le RecursiveRegexIterator.

Nous commençons par une classe de base dont le travail principal est de conserver l'expression rationnelle avec laquelle nous voulons filtrer, tout le reste est reporté au RecursiveRegexIterator. Notez que la classe est abstract car elle ne fait pas quoi que ce soit d'utile: le filtrage proprement dit doit être fait par les deux classes qui vont l'étendre. En outre, il peut être appelé FilesystemRegexFilter mais rien ne l'oblige (à ce niveau) à filtrer les classes liées au système de fichiers (j'aurais choisi un meilleur nom, si je n'étais pas si endormi).

abstract class FilesystemRegexFilter extends RecursiveRegexIterator { 
    protected $regex; 
    public function __construct(RecursiveIterator $it, $regex) { 
     $this->regex = $regex; 
     parent::__construct($it, $regex); 
    } 
} 

Ces deux classes sont des filtres très basiques, agissant respectivement sur le nom du fichier et du répertoire.

class FilenameFilter extends FilesystemRegexFilter { 
    // Filter files against the regex 
    public function accept() { 
     return (! $this->isFile() || preg_match($this->regex, $this->getFilename())); 
    } 
} 

class DirnameFilter extends FilesystemRegexFilter { 
    // Filter directories against the regex 
    public function accept() { 
     return (! $this->isDir() || preg_match($this->regex, $this->getFilename())); 
    } 
} 

Pour mettre ceux en pratique, les itère suivantes récursive sur le contenu du répertoire dans lequel le script réside (ne hésitez pas à modifier cela!) Et filtre les .Trash dossiers (en vous assurant que les noms de dossier faire correspondre la regex spécialement conçu), et en acceptant seulement les fichiers PHP et HTML.

$directory = new RecursiveDirectoryIterator(__DIR__); 
// Filter out ".Trash*" folders 
$filter = new DirnameFilter($directory, '/^(?!\.Trash)/'); 
// Filter PHP/HTML files 
$filter = new FilenameFilter($filter, '/\.(?:php|html)$/'); 

foreach(new RecursiveIteratorIterator($filter) as $file) { 
    echo $file . PHP_EOL; 
} 

À noter en particulier que, puisque nos filtres sont récursives, nous pouvons choisir de jouer avec la façon de itérer sur eux. Par exemple, nous en faisant pourrait facilement nous limiter à seulement numériser jusqu'à profonds 2 niveaux (y compris le dossier de départ):

$files = new RecursiveIteratorIterator($filter); 
$files->setMaxDepth(1); // Two levels, the parameter is zero-based. 
foreach($files as $file) { 
    echo $file . PHP_EOL; 
} 

Il est également très facile d'ajouter des filtres encore plus (par instanciation plus de notre filtrage classes avec des expressions régulières différentes ou, en créant de nouvelles classes de filtrage) pour des besoins de filtrage plus spécialisés (par exemple, taille de fichier, longueur de chemin complet, etc.).

P.S. Hmm cette réponse babille un peu; J'ai essayé de le garder aussi concis que possible (même en enlevant de vastes pans de super-babillage). Toutes mes excuses si le résultat net laisse la réponse incohérente.

+0

Vraiment apprécier l'approche moins rapide (et moins sale), il montre exactement ce que je cherche. Merci. Bien que rapide et sale a fait l'erreur avec Fatal error: Uncaught exception 'UnexpectedValueException' message « RecursiveDirectoryIterator :: __ construct (/var/www/html/.Trash-0) – Chris

+1

L'erreur est vraiment rien de mal avec la code (la barre n'essaye pas assez), la cause la plus probable est les permissions du dossier (ou son absence). Heureux que tu sois heureux avec la meilleure alternative de toute façon. :) – salathe

+0

Très bien, mais comment obtenir un objet SplFileInfo pour chaque fichier, plutôt qu'un simple chemin? –

8

Les documents ne sont en effet pas très utiles. Il y a un problème à l'aide d'une expression rationnelle pour « ne correspond pas », mais nous allons illustrer un exemple de travail d'abord:

<?php 
//we want to iterate a directory 
$Directory = new RecursiveDirectoryIterator("/var/dir"); 

//we need to iterate recursively 
$It  = new RecursiveIteratorIterator($Directory); 

//We want to stop decending in directories named '.Trash[0-9]+' 
$Regex1 = new RecursiveRegexIterator($It,'%([^0-9]|^)(?<!/.Trash-)[0-9]*$%'); 

//But, still continue on doing it **recursively** 
$It2  = new RecursiveIteratorIterator($Regex1); 

//Now, match files 
$Regex2 = new RegexIterator($It2,'/\.php$/i'); 
foreach($Regex2 as $v){ 
    echo $v."\n"; 
} 
?> 

Le problème est le ne correspond pas à .Trash[0-9]{3} partie: La seule façon que je sais comment négatif correspondre le répertoire, est correspondre la fin de la chaîne $, puis affirmer avec un lookbehind (?<!/foo) 'si elle n'est pas précédée par'/foo '.

Cependant, comme .Trash[0-9]{1,3} n'est pas une longueur fixe, nous ne pouvons pas l'utiliser comme une assertion lookbehind. Malheureusement, il n'y a pas de 'correspondance inversée' pour un RegexIterator. Mais peut-être il y a plus de gens regex-avertis alors je sachant comment faire correspondre « une chaîne ne se termine pas avec .Trash[0-9]+


modifier: Je l'ai '%([^0-9]|^)(?<!/.Trash-)[0-9]*$%' comme regex ferait l'affaire.

+0

Appréciez la solution était simple et facile à comprendre. – Chris

+0

le $ It var est unreference –

1

Une amélioration à salathe, serait d'oublier la classe abstraite personnalisée. Il suffit d'utiliser un bon POO en PHP et étendre directement RecursiveRegexIterator à la place:

Voici le filtre de fichier

class FilenameFilter 
extends RecursiveRegexIterator 
{ 
    // Filter files against the regex 
    public function accept() 
    { 
     return ! $this->isFile() || parent::accept(); 
    } 
} 

Et le filtre Directory

class DirnameFilter 
extends RecursiveRegexIterator 
{ 
    // Filter directories against the regex 
    public function accept() { 
     return ! $this->isDir() || parent::accept(); 
    } 
} 
+0

Remarque: ce comportement est différent de mon exemple. Le vôtre correspond à l'expression régulière quelle que soit la valeur "actuelle" de l'itérateur en cours de filtrage (pour 'FilesystemIterator', la valeur" actuelle "peut être manipulée en utilisant des drapeaux). Mon exemple utilise uniquement le nom de fichier. – salathe