http://qs1969.pair.com?node_id=780335

transiency has asked for the wisdom of the Perl Monks concerning the following question:

I've written a script to handle all those annoying !unix naming conventions i often run across. It renames anything not a \w.~/- to _. It works great except it finds and traverses .. and . which cause it to escape from the intended directory it was suppose to do its operations on. I cant seem to find the source of this bug, anyone help a monk out? Thanks.

PS i tried nexting out on . and .. but it just got stuck in a nexting loop (appearantly infinite)
#!/usr/bin/perl use warnings; use strict; use File::Rename qw(rename); use File::Find; our $VERSION = '0.1.3'; ### NAME NORMALIZER ### my @torename = (); my @dirs = (); my $DIR = shift @ARGV; opendir(TOCLEAN, "$DIR") or die $!; @dirs = sort readdir(TOCLEAN); @dirs = map { $_ = $DIR . $_ } @dirs; find(\&cleanup, @dirs); sub cleanup{ return if !stat; my $name = $File::Find::name; my $dir = $File::Find::dir; #next if($name =~ /[^\.]|[^\.\.]/g); if(/[^a-zA-Z0-9_\-\/\.~]/){ print "$name\n"; push(@torename, "$name"); } } if(@torename > 0){ print "Renaming files now..\n"; sleep 1; rename @torename, sub { s/[^a-zA-Z0-9_\-\/\.~]/_/g }, 1; } else{print "No files found to rename.. Exiting\n"};
foreach(@the_wicked){sleep(0);}

Replies are listed 'Best First'.
Re: File::Find finding . and ..
by moritz (Cardinal) on Jul 15, 2009 at 14:44 UTC
    Without reading the rest of your post:
    next if($name =~ /[^\.]|[^\.\.]/g);

    I think this might work better:

    return if ($_ eq '.' || $_ eq '..');
      Thanks for the tip, however it is still traversing ..

      video/../temp/media/music any ideas?

      foreach(@the_wicked){sleep(0);}
        What is it? Show your new code.
        Got it. if($name !~ /\/\.\.?\//) was the trick that worked.

        Thanks for the help everyone.

        #!/usr/bin/perl use warnings; use strict; use File::Rename qw(rename); use File::Find; our $VERSION = '0.1.4'; ### NAME NORMALIZER ### $|++; my (@dirs, @torename) = (); my $DIR = shift @ARGV; opendir(TOCLEAN, "$DIR") or die $!; @dirs = map { $_ = $DIR . $_ } sort readdir(TOCLEAN); find(\&cleanup, @dirs); if(@torename > 0){ print "Renaming files now..\n"; sleep 1; rename @torename, sub { s/[^a-zA-Z0-9_\-\/\.~]/_/g }, 1; } else{ print "No files found to rename.. Exiting\n" } sub cleanup{ return if !stat; my $name = $File::Find::name; my $dir = $File::Find::dir; next if ($name eq '.' or $name eq '..'); if($name !~ /\/\.\.?\//){ if(/[^a-zA-Z0-9_\-\/\.~]/){ print "Found: $name\n"; push(@torename, "$name"); } } }
        foreach(@the_wicked){sleep(0);}
Re: File::Find finding . and ..
by davorg (Chancellor) on Jul 15, 2009 at 15:19 UTC
    next if($name =~ /[^\.]|[^\.\.]/g);

    That really isn't doing what you think it is.

    "If name matches a character that isn't a dot or a character that isn't a dot".

    You probably meant something like:

    next if ($name =~ /^(\.\.?)$)/);

    But this is one of those cases where a regex is overkill.

    next if ($name eq '.' or $name eq '..');

    Update: And that next should really be a return.

    --

    See the Copyright notice on my home node.

    Perl training courses

Re: File::Find finding . and ..
by jwkrahn (Abbot) on Jul 15, 2009 at 17:18 UTC

    Your program, as presented, will not work.   Suppose that File::Find::find created a list something like:

    /home/transiency/dir*one
    /home/transiency/dir*one/file*one
    /home/transiency/dir*one/file*two

    After you rename '/home/transiency/dir*one' to '/home/transiency/dirone' then your program will not be able to find and rename '/home/transiency/dir*one/file*one' and '/home/transiency/dir*one/file*two' because '/home/transiency/dir*one' does not exist anymore.   You need something like:

    #!/usr/bin/perl use warnings; use strict; use File::Find; our $VERSION = '0.1.3'; ### NAME NORMALIZER ### my $DIR = shift @ARGV; opendir TOCLEAN, $DIR or die "$DIR: $!"; my @dirs = map "$DIR/$_", grep -d "$DIR/$_" && !/\A\.\.?\z/, readdir T +OCLEAN; finddepth sub { ( my $new = $_ ) =~ tr!a-zA-Z0-9.~-!_!c; return if $new eq $_; rename $_, $new or warn "Cannot rename '$_' to '$new' $!"; }, @dirs;