Rekurzivní glob()

Školení, která pořádám

Funkci glob mám v oblibě, ale chybí mi u ní modifikátor pro rekurzivní procházení adresářů. Toho by se nejspíš dalo nějak dosáhnout zkombinováním tříd RecursiveDirectoryIterator, RecursiveIteratorIterator a GlobIterator, ale přišlo mi to tak krkolomné, že jsem si raději napsal funkci použitelnou se stejnou lehkostí jako původní glob:

<?php
/** Nalezení souborů vyhovujících dané masce ve všech podadresářích
* @param string se stejnými zástupnými znaky jako glob()
* @param int stejné jako glob()
* @return array nalezené soubory
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function rglob($pattern, $flags = 0) {
    $return = glob($pattern, $flags);
    $dirs = "*";
    $separator = DIRECTORY_SEPARATOR;
    $files = $pattern;
    if (preg_match('~(.*)([/\\\\])(.+)~', $pattern, $match)) {
        $dirs = "$match[1]$match[2]*";
        $separator = $match[2];
        $files = $match[3];
    }
    foreach (glob($dirs, ($flags & ~GLOB_MARK) | GLOB_ONLYDIR) as $subdir) {
        $return = array_merge($return, rglob("$subdir$separator$files", $flags));
    }
    return $return;
}
?>

Funkce si poradí se všemi obvyklými vstupy:

Dbá také na to, aby byl výstup konzistentní: pokud jí předáte dir/*.xml, tak na Windows nevrátí dir/subdir\a.xml. Stejně tak pro *.xml nevrátí ./a.xml. Kvůli tomu je trochu složitější, než by na první pohled mělo být potřeba.

Zásadní nevýhodou této funkce je, že nejprve získá seznam všech souborů, uloží ho do paměti a pak je teprve můžeme procházet. Vzhledem k mému nejobvyklejšímu využití této funkce (jednorázové skripty spouštěné z příkazové řádky) mi to ale nevadí. Vyřešit by se to dalo přepsáním na iterátor. S podporou operátoru yield (v HipHopu pro PHP nebo v PHP 5.5) by to vypadalo skoro stejně:

<?php
/** Nalezení souborů vyhovujících dané masce ve všech podadresářích
* @param string se stejnými zástupnými znaky jako glob()
* @param int stejné jako glob()
* @return array nalezené soubory
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function rglob_yield($pattern, $flags = 0) {
	foreach (glob($pattern, $flags) as $filename) {
		yield $filename;
	}
	$dirs = "*";
	$separator = DIRECTORY_SEPARATOR;
	$files = $pattern;
	if (preg_match('~(.*)([/\\\\])(.+)~', $pattern, $match)) {
		$dirs = "$match[1]$match[2]*";
		$separator = $match[2];
		$files = $match[3];
	}
	foreach (glob($dirs, ($flags & ~GLOB_MARK) | GLOB_ONLYDIR) as $subdir) {
		foreach (rglob_yield("$subdir$separator$files", $flags) as $filename) {
			yield $filename;
		}
	}
}

foreach (rglob_yield('www/api/*.php') as $filename) {
	echo "$filename\n";
}
?>
Jakub Vrána, Řešení problému, 8.10.2012, diskuse: 5 (nové: 0)

Diskuse

Michal:

Jenom upozorním na drobnost, v hlavičce druhé funkce:
function rglob_yeild($pattern, $flags = 0) {
je překlep y*ei*ld.

hever:

a ještě v druhé funkci nevidím return...

ikona Jakub Vrána OpenID:

To je právě smysl operátoru yield… Místo jednoho return můžu mít více yield.

ozz:

`yield return` má c#, že?

ikona Jakub Vrána OpenID:

Díky za upozornění, opravil jsem to.

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.