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

Hello all, I wrote a code that renames files, such that it changes the extension of any file to .xml extension. The program is recursive to traverse all files and subdirectories in a certain directory, and it takes a path of a directory as argument. However, the code I wrote does the renaming only for the first file in the folder and then displays an error message saying "No such file or directory" at the line performing renaming for any other file. I don't know why it is doing so. Can anyone please help me?
use Cwd; # module for finding the current working directory $|=1; # turn off I/O buffering # This subroutine takes the name of a directory and recursively scans # down the filesystem from that point looking for files named "core" sub ScanDirectory{ my ($workdir) = shift; print "Work dir = $workdir\n"; my ($startdir) = &cwd; # keep track of where we began chdir($workdir) or die "Unable to enter dir $workdir:$!\n"; opendir(DIR, ".") or die "Unable to open $workdir:$!\n"; my @names = readdir(DIR) or die "Unable to read $workdir:$!\n"; closedir(DIR); foreach my $name (@names){ next if ($name eq "."); next if ($name eq ".."); if (-d $name){ # is this a directory? &ScanDirectory($name); next; } else { # this is a file print "Name = $name\n"; $newName = $name; $newName =~ s/\..*//; $newName .= ".xml"; $result = rename($name, $newName) or die "cannot rename $name to $newName:$!"; } chdir($startdir) or die "Unable to change to dir $startdir:$!\n"; } } &ScanDirectory($ARGV[0]);

Replies are listed 'Best First'.
Re: Perl rename method
by FunkyMonk (Bishop) on Dec 24, 2007 at 18:19 UTC
    chdir($startdir) should be outside the loop, shouldn't it?

    However, I'd look at a rewrite using File::Find or File::Find::Rule. The latter especially simplifies the task:

    use File::Find::Rule; my $start = shift || '.'; for my $oldname ( File::Find::Rule->file->in( $start ) ) { ( my $newname = $oldname ) =~ s/\..*?$//; rename $oldname, "$newname.xml" or die "..."; }

Re: Perl rename method
by ikegami (Patriarch) on Dec 24, 2007 at 18:39 UTC
    chdir($workdir) or die "Unable to enter dir $workdir:$!\n"; ... foreach my $name (@names){ ... if (-d $name){ &ScanDirectory($name); <-- This changes chdir. next; <-- Never changed back. } ... } ...

    Fix:

    use File::Spec::Functions qw( catfile ); # This subroutine takes the name of a directory and recursively scans # down the filesystem from that point looking for files named "core" sub ScanDirectory{ my $workdir = shift; if ($workdir =~ /^[a-zA-Z]:\z/) { $workdir .= '.'; # Fix bug in Windows. } print "Work dir = $workdir\n"; opendir(local *DIR, $workdir) or die "Unable to open $workdir: $!\n"; my @names = readdir(DIR) or die "Unable to read $workdir: $!\n"; closedir(DIR); foreach my $name (@names){ next if ($name eq "."); next if ($name eq ".."); my $fqname = catfile($workdir, $name); if (-d $fqname){ # is this a directory? ScanDirectory($fqname); next; } print "Name = $name\n"; $newName = $name; $newName =~ s/\..*//; $newName .= ".xml"; my $new_fqname = catfile($workdir, $newName); $result = rename($fqname, $new_fqname) or die "Cannot rename $fqname to $new_fqame: $!"\n; } } $|=1; # Turn off I/O buffering ScanDirectory($ARGV[0]);

    Better yet, let's remove needless recursion.

    use File::Spec::Functions qw( catfile ); # This subroutine takes the name of a directory and recursively scans # down the filesystem from that point looking for files named "core" sub ScanDirectory{ my $workdir = shift; if ($workdir =~ /^[a-zA-Z]:\z/) { $workdir .= '.'; # Fix bug in Windows. } my @todo = ( $workdir ); while (@todo) { my $workdir = shift(@todo); print "Work dir = $workdir\n"; opendir(local *DIR, $workdir) or die "Unable to open $workdir: $!\n"; my @names = readdir(DIR) or die "Unable to read $workdir: $!\n"; closedir(DIR); foreach my $name (@names){ next if ($name eq "."); next if ($name eq ".."); my $fqname = catfile($workdir, $name); if (-d $fqname){ # is this a directory? push @todo, $fqname; next; } print "Name = $name\n"; $newName = $name; $newName =~ s/\..*//; $newName .= ".xml"; my $new_fqname = catfile($workdir, $newName); $result = rename($fqname, $new_fqname) or die "Cannot rename $fqname to $new_fqame: $!"\n; } } } $|=1; # Turn off I/O buffering ScanDirectory($ARGV[0]);

    Btw, I properly scoped DIR (added local *DIR;) and removed requests to ignore prototypes (removed &).

    Update: Added fix.