rename_recursive

Gepost door Sjaak Ringens op 27-07-2011 02:06.

Ik heb deze functie gemaakt omdat ik een map (met submappen) met zeker 5000+ foto's moest gaan hernoemen zodat elke foto een unieke naam heeft (in dezelfde map) en dit natuurlijk erg lang gaat duren als ik dit handmatig moet gaan doen!
De bestanden worden hernoemd met de willekeurige karakters [a-z0-9].

Om het script toch wat bruikbaarder te maken heb ik extra functies toegevoegd:

Functies
- Je kunt aangeven uit hoeveel karakters het nieuwe bestand moet bestaan (max 128).
- Je kunt aangeven welke bestanden niet hernoemd mogen worden.
- Je kunt aangeven welke bestandstypes niet hernoemd mogen worden.
- Je kunt aangeven of bestanden met het juiste formaat overgeslagen mogen worden, of

geforceerd nog eens hernoemen.
- Je kunt aangeven of de bestanden in onderliggende mappen ook hernoemd moeten worden.

Let op

Je hebt minimaal PHP versie 5.3 nodig voor deze functie.

Bestanden van dit script

rename_recursive.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?php
ini_set( 'display_errors', 'On' );
error_reporting( E_ALL | E_STRICT );


/***
* In welke map staan de bestanden?
* hier = map waar dit bestand in staat.
*/
$sFolder = dirname( __FILE__ );

/***
* Hoe lang moeten de nieuwe unieke bestandsnamen worden?
* maximumlengte voor deze functie is 128 tekens
*/
$iFileNameLength = 8;

/***
* Moeten ook de bestanden uit onderliggende mappen worden hernoemd?
* true = ja
* false = nee
*/
$bIncludeSubFolders = true;

/***
* Alle bestanden hernoemen, ook al zijn ze in het goede formaat?
* true  = ja
* false = nee
*/
$bForceRename = false;

/***
* Hier kun je opgeven welke bestanden NIET hernoemd moeten worden.
* Dit bestand hernoemen we natuurlijk niet, dus deze staat al erbij.
* Let op dat je hier de extensies van de bestanden ook neerzet!
*/
$aExcludedFileNames = array( basename( __FILE__ ), 'nog_een_bestand.txt' );

/***
* Hier kun je opgeven welke bestandsextensies NIET hernoemd moeten worden.
* php-bestanden gaan we nu even niet hernoemen, dus deze sluiten we alvast uit.
* Extensies zijn niet hoofdlettergevoelig.
*/
$aExcludedExtensions = array( 'php', 'nog_een_extensie' );



/**************************************************/
/*** Hieronder hoef je niets meer aan te passen ***/
/**************************************************/



set_time_limit( 0 );

function rename_recursive( $psFolder, $pbForceRename, $piFileNameLength, $paExcludedFileNames, $paExcludedExtensions, $pbIncludeSubFolders )
{
    if( false === is_dir( $psFolder ) )
        trigger_error( 'De opgegeven map is niet geldig' );
    if( false === is_bool( $pbForceRename ) )
        trigger_error( 'De tweede parameter ($pbForceRename) moet een boolean zijn' );
    if( false === ctype_digit( (string)$piFileNameLength ) )
        trigger_error( 'De derde parameter ($piFileNameLength) moet een integer zijn' );
    if( false === is_array( $paExcludedFileNames ) )
        trigger_error( 'De vierde parameter ($paExcludedFileNames) moet een array zijn' );
    if( false === is_array( $paExcludedExtensions ) )
        trigger_error( 'De vijfde parameter ($paExcludedExtensions) moet een array zijn' );
    if( false === is_bool( $pbIncludeSubFolders ) )
        trigger_error( 'De zesde parameter ($pbIncludeSubFolders) moet een boolean zijn' );

    $oDirectoryIterator = new RecursiveDirectoryIterator( $psFolder );
    
    foreach( $oDirectoryIterator as $sFileInfo )
    {
        if( true === is_dir( $sFileInfo ) )
            if( true === $pbIncludeSubFolders )
                rename_recursive( $sFileInfo, $pbForceRename, $piFileNameLength, $paExcludedFileNames, $paExcludedExtensions, $pbIncludeSubFolders );
        else
        {
            $sFolder = substr( $sFileInfo, 0, strrpos( $sFileInfo, DIRECTORY_SEPARATOR ) );
            $sOldFileName = strstr( substr( $sFileInfo, strrpos( $sFileInfo, DIRECTORY_SEPARATOR ) + 1 ), '.', true );
            $sFileExtention = substr( $sFileInfo, strrpos( $sFileInfo, '.' ) + 1 );

            if( true === in_array( $sOldFileName, $paExcludedFileNames ) )
                continue;
            if( true === in_array( strtolower( $sFileExtention ), array_map( 'strtolower', $paExcludedExtensions ) ) )
                continue;
            if( false === $pbForceRename && strlen( $sOldFileName ) === $piFileNameLength && true === ctype_alnum( $sOldFileName ) )
                continue;

            $sNewFileName = strtolower( substr( hash( 'sha512', microtime( ) ), rand( 0, ( 128 - $piFileNameLength ) ), $piFileNameLength ) . '.' . $sFileExtention );

            while( true === is_file( $psFolder . DIRECTORY_SEPARATOR . $sNewFileName ) )
                $sNewFileName = strtolower( substr( hash( 'sha512', microtime( ) ), rand( 0, ( 128 - $piFileNameLength ) ), $piFileNameLength ) . '.' . $sFileExtention );

            echo $sFolder . DIRECTORY_SEPARATOR . $sOldFileName . '.' . $sFileExtention . ' => ' .  $sFolder . DIRECTORY_SEPARATOR . $sNewFileName . PHP_EOL;
            rename( $sFolder . DIRECTORY_SEPARATOR . $sOldFileName . '.' . $sFileExtention, $sFolder . DIRECTORY_SEPARATOR . $sNewFileName );
        }
    }
}

rename_recursive( $sFolder, $bForceRename, $iFileNameLength, $aExcludedFileNames, $aExcludedExtensions, $bIncludeSubFolders );

?>

Commentaar

27-07-2011 10:51

Kleine toevoeging, als je toch minimaal PHP 5.3 moet hebben kun je beter __DIR__ gebruiken in plaats van dirname( __FILE__ ). :-)

27-07-2011 11:19

Een aantal dingen zijn mijn niet duidelijk of kunnen beter als je het mij vraagt.

- echo in een functie is not done! Misschien een reference meegeven voor eventueel loggen van de output
- je gebruikt een RecursiveDirectoryIterator maar waarom dan geen RecursiveIteratorIterator?
- is_dir( $sFileInfo ) is onnodig omdat FileInfo een isDir functie heeft.
- Waarom zou je hier php5.3 voor nodig hebben? 5.2 voor de iterators misschien, maar zie verder niks spannends.
- false === is_dir( $psFolder ) is dat echt nodig? controleerd de itterator dat niet?
- je gebruikt is_array() voor een paar parameters. je kan dit afdwingen met typehinting function a(array $option){

27-07-2011 12:44

@Richard
Inderdaad, zal het veranderen :)

@Lode
1. Die echo is ook alleen om te debuggen, heb hem hier in een comment gezet. Als je de echo gebruikt i.p.v. de rename(), kun je eerst kijken wat er gaat veranderen, voordat het ook daadwerkelijk veranderd ;) En om hier nu echt een logger te gaan voor maken, neu, dat was te veel van het goede voor mijn situatie (be my guest als je dit graag nog wilt toevoegen!)

2. Wat is het verschil? De enige reden dat ik dit ben gaan gebruiken is omdat ik glob niet fatsoenlijk werkend kreeg! Ben geen held in de standaard php classes :D

3. Wat is het verschil dan tussen de normale is_dir functie, en de functie die de Iterator biedt?

4. Voor de functie strstr() heb ik gebruik gemaakt van de derde parameter, welke alleen beschikbaar is vanaf 5.3

5. Leek me handig om eerst alles te controleren, voordat je verder gaat met een functie. En daarnaast: beter te veel controleren dat te weinig, toch?

6. Typehinting kan ik inderdaad gebruiken, niet aan gedacht, dus wordt er bij gezet.

Thanks voor de opmerkingen :)

27-07-2011 17:21

Even snel ook een versie gemaakt.
Draai geen 5.3 op m'n werk pc dus kan jou versie niet testen en weet niet helemaal wat ie moet doen.
Ook een beetje overbodig misschien om voor die ene functie 5.3 af te dwingen en m'dan jier te posten.

Als er verschillen in zitten mag het geen probleem zijn die aan te passen denk ik.

Is zo toch een stukje netter en simpeler als je het mij vraagt?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
ini_set('display_errors', 1);
ini_set('error_reporting', E_ALL);

function renamer($directory, $length, array $exclude = array(), array $extensions = array(), $recursive = true){
    static $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    
    if(!ctype_digit((string) $length) && ($length < 5 ^ $length > 128)){
        trigger_error('length must be a number', E_USER_ERROR);
    }
    $directory = realpath($directory).DIRECTORY_SEPARATOR;
    try{
        $iterator = ($recursive) ?
            new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)):
            new DirectoryIterator($directory);
    }
    catch(Exception $e){
        trigger_error($e, E_USER_ERROR);
    }
    foreach($iterator as $file){
        $extension     = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        if(    $file->isDir() ||     //skip directories
            (!!$extensions && in_array($extension, $extensions)) || //skip extension
            (!!$exclude && in_array($file->getFilename(), $exclude)) //skip excluded filesname
        ){
            continue;
        }
        while(true){
            $destination = $file->getPath().DIRECTORY_SEPARATOR.substr(str_shuffle($characters), 0, $length).'.'.$extension;
            if(!file_exists($destination)){
                if(false === rename($file, $destination)){
                    trigger_error(sprintf('failed renaming file "%s"', $file), E_USER_ERROR);
                }
                break;
            }
        }
    }
}
renamer(dirname(__FILE__).'/files', 10);
?>
27-07-2011 18:53

@ Lode

Persoonlijk vind ik mijn script dan behoorlijk duidelijker :)
Als die try/catch/if/else zo wild door elkaar

1. Waarom gebruik jij niet range() om de karakters te "selecteren"?

2. Waarom dubbele uitroeptekens gebruiken? Als $bool true is, lever !$bool false op, maar !!$bool toch weer true? Dan kun je toch net zo goed meteen $bool neerzetten?

3. Kun je dat iterator-gedoe eens verder toelichten? Ik heb het bij mij werkend gekregen, maar snap vanuit de handleiding eigenlijk 0,0 waarvoor het bruikbaar is =/

28-07-2011 01:00

@Sjaak,

Die try catch is om errors af te vangen die je bij een niet bestaand pad bijvoorbeeld zou krijgen vanuit de iterator zonder daar zelf nog een keer op te gaan controleren. Wiel opnieuw uitvinden komt ter gedachte. Gewoon gebruiken wat er al is dus.

Range kan ook wellicht, maar str_shuffle wil een string.
Scheelt misschien weer een overbodige functie aanroep.

!! gebruik je om iets positief te casten naar boolean.
Scheelt een is_bool aanroep en is daardoor weer losser zodat je ook 0 of 1 zou kunnen gebruiken ook.

Een RecursiveIterator kan je als een gewone iterator benaderen door hem in een RecursiveIteratorIterator te stoppen. Hier wel makkelijk omdat we switchen tussen een DirectoryIterator en een RecursiveDirectoryIterator. Anders zou je voor beide een aparte procedure moeten schrijven om ze te doorlopen.
Zijn inderdaad niet altijd even goed gedocumenteerd.
Ik weet door er heel veel mee te werken inmiddels aardig hoe ze werken en waarvoor je ze kan gebruiken. Wat wil je precies weten? Misschien een keer een wiki artikel schrijven anders?

27-08-2011 20:06

Is het niet makkelijker om gewoon een volgorde na te lopen? Wanneer de naamlengte niet erg ruim gekozen wordt, kan er op het einde een hoop iteraties uitgespaard worden op deze manier.

Misschien is het ook een idee om een check toe te voegen om te kijken of de naamlengte wel groot genoeg is voor het aantal bestanden.

Daarnaast misschien een optie voor een pre- en suffix?

Inloggen wachtwoord vergeten? Aanmelden