I originally made this script to remove white space from my files and directories names,
then I edited it a bit to rename files based on a pattern and replacement string.
I am still a beginner, so any comments would be appreciated.
(Sorry for my bad English.)

#!/usr/bin/perl =head1 Usage: rename-script [OPTIONS] PATH Options: -p Pattern to look for in file's name. Default '\s' -r Replacement string. Default '_' -n Don't use 'g' option modifier with pattern matching =cut use strict; use File::Spec; use File::Basename; use File::Find; use Getopt::Std; my %opts; getopts('np:r:', \%opts); my $path = shift @ARGV // die "need path argument.\n"; my $pat = $opts{p} // '\s'; my $rep = $opts{r} // '_'; my @found; die "bad pattern $pat\n" unless eval { "" =~ /$pat/; 1; }; find sub { unshift @found, $File::Find::name }, $path; for ( @found ) { my ($base,$dir) = (basename($_),dirname($_)); my $newbase; defined $opts{n} ? (($newbase = $base) =~ s/$pat/$rep/ ) : (($newbas +e = $base) =~ s/$pat/$rep/g ); if ( $newbase ne $base ) { my $newname = File::Spec->catfile($dir,$newbase); (-e $newname) and (warn("can't rename $_ to $newname: $newname alr +eady exists\n"),next) ; rename($_,$newname) or warn "can't rename $_ to $newname:$!\n"; } }

Replies are listed 'Best First'.
Re: Recursive renaming script
by hbm (Hermit) on Dec 02, 2011 at 17:02 UTC

    Pretty heady stuff for a beginner, and I like your usage block.

    One thing jumps out at me. Rather than:

    find sub { push @found, ... } for (reverse @found) {

    Why not:

    find sub { unshift @found, ... } for (@found) {

      Thanks, this saves a useless call to reverse and reads better. Updated the script.

Re: Recursive renaming script
by toolic (Bishop) on Dec 02, 2011 at 17:20 UTC
    You could convert those comments into POD and get a manpage for free with perldoc:
    =head1 Usage: rename-script [OPTIONS] PATH Options: -p Pattern to look for in file's name. Default '\s' -r Replacement string. Default '_' -n Don't use 'g' option modifier with pattern matching =cut
    Then at your command prompt...
    $ perldoc rename-script
    You could get even fancier with Pod::Usage.

      Cool, I had no prior experience with documenting stuff. Replaced the comments, thanks.

Re: Recursive renaming script
by aaron_baugher (Curate) on Dec 02, 2011 at 21:01 UTC

    Just out of curiosity: why put all your filenames in an array, and then loop through the array to work on them? I'm guessing you did that to get a depth-first traversal, so it wouldn't change a directory name and then be unable to get at the files beneath it. If that's the case, you could use the finddepth() method of File::Find.

    Then you could just put the stuff inside your for loop into the sub you're passing to finddepth(). That'd avoid the potential problem of @found filling a lot of memory on a large directory structure, and you wouldn't need to call basename() and dirname() because your callback would already get those values in $File::Find::dir and $_.

    Aaron B.
    My Woefully Neglected Blog, where I occasionally mention Perl.

      Many thanks, I didn't know about finddepth().
      I still have the knowledge which books for starting programmers offer, I haven't read any of the modules documentation yet, I guess learning the language itself is the trivial part :)
      I won't modify the code to prevent confusion for someone who might read the answers.
Re: Recursive renaming script
by hbm (Hermit) on Dec 02, 2011 at 19:40 UTC

    After a closer look, I notice you have ($newbase = $base) on both conditions of your tertiary. My first thought was instead, my $newbase = $base;.

    But, you only need $newbase if $base matches the pattern. Further, you stop using $base; you may as well make changes to it rather than $newbase.

    So, perhaps this:

    ... for ( @found ) { my ($base,$dir) = (basename($_),dirname($_)); next unless $base =~ /$pat/; defined $opts{n} ? $base =~ s/$pat/$rep/ : $base =~ s/$pat/$rep/g; my $newname = File::Spec->catfile($dir,$base); if (-e $newname) { warn("can't rename $_ to $newname: $newname already exists\n"); } else { rename($_,$newname) or warn "can't rename $_ to $newname:$!\n"; } }

      Actually that's the way I wanted to do it initially, but then I thought that matching the same pattern twice seemed like a bad idea.
      Now I notice that the second pattern only gets processed if the file is to be renamed which is not so often, thanks.

Re: Recursive renaming script
by i5513 (Pilgrim) on Jan 04, 2012 at 14:48 UTC

    See prename from Debian perl git files. It is named rename in Debian by default

    It is based in a Larry Wall script !

    :-)