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

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

I have a bunch of files in my directories that I need to mass rename. I'm trying to get a script to change filename extensions in a directory, my code runs but it doesn't do anything. Any help is appreciated!

#!/usr/bin/perl use File::Basename qw(fileparse); @ARGV == 3; my $dir = $ARGV[0]; my $old = $ARGV[1]; my $new = $ARGV[2]; opendir(my $path, $dir); my @list = readdir($path); foreach(@list){ my $name = fileparse("$_", ".$old"); rename("$name.$old", "$name.$new"); };

Replies are listed 'Best First'.
Re: Changing filename extensions
by Fletch (Bishop) on Nov 16, 2021 at 03:45 UTC

    Always check the return value from system calls like rename; you'd see that it failed. The readdir returns names of files in $dir but they're not prefixed with it so your rename("$name.$old","$name.$new") is trying to rename files in the directory you ran from not the one you named in $dir (you'd need to use "$dir/$name.$old" instead).

    You might also check out something like Path::Tiny for another interface which might be easier to work with.

    Edit: And just for clarity, by checking the return value I mean when you call things like opendir or rename that interact with the OS you should check their return value and print a meaningful error message when things go wrong. E.g.:

    opendir( my $dirhandle, $dir ) or die "Can't opendir directory '$dir': + $!\n"; rename( "$dir/$name.$old", "$dir/$name.$new" ) or warn "Problem renaming '$dir/$name.$old' to '$dir/$name.$new': $! +\n";

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Okay so I have it working now! But I have one more question, is there a way to get my code to also account for files in subdirectories of the directory?

        This may work (UNTESTED):

        #!/usr/bin/perl use strict; use warnings; use File::Find; @ARGV == 3 or die "usage: $0 DIR OLD_EXT NEW_EXT\n"; my $dir = $ARGV[ 0 ]; my $old = $ARGV[ 1 ]; my $new = $ARGV[ 2 ]; my @list; find sub { push @list, $File::Find::name if -f && /\.\Q$old\E\z/; }, $dir; foreach my $old_name ( @list ) { my $new_name = $old_name =~ s/\.\Q$old\E\z/.$new/r; rename $old_name, $new_name or die "Cannot rename '$old_name' beca +use: $!"; }

        You could use the -d test to look for entries that are themselves directories and then recurse down into them but at that point you'd have started down the path of reimplementing what something like Path::Tiny (or File::Find, or File::Find::Rule, or Path::Iterator::Rule, or . . .) could do for you off the shelf so you'd be better of using that to begin with.

        ## presuming your sample vars for dir, old, new ## Somewhat untested, but . . . use Path::Tiny qw( path ); my $iter = path( $dir )->iterator( { recurse => 1 } ); while( my $path = $iter->() ) { next unless $path->is_file and $path =~ m{\. $old $}x; my $new_path = $path->basename( $old ) . ".$new"; unless( my $ret = $path->move( $new_path ) ) { warn "Problem moving '$path': $ret\n"; } }

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Re: Changing filename extensions
by jwkrahn (Abbot) on Nov 16, 2021 at 03:44 UTC

    $dir is not the current directory so you are renaming nonexistant files in the current directory.

    Either change the current directory to where the files are or include $dir as part of the path when renaming.

      Forgive me I'm a newbie at Perl, how do I include $dir as part of the path?

        rename("$dir/$name.$old", "$dir/$name.$new");
Re: Changing filename extensions
by haukex (Archbishop) on Nov 16, 2021 at 07:44 UTC

    Just to add to what the others have said about checking for errors: always Use strict and warnings. You may also be interested in autodie as an alternative to checking every call for errors.

    Personally, when batch renaming files, I tend to use Larry's rename script from File::Rename. For recursive operations I would combine that with find.

Re: Changing filename extensions
by hippo (Bishop) on Nov 16, 2021 at 09:35 UTC

    The third line of your script is

    @ARGV == 3;

    It would be interesting to know what you think this line is doing and why it is there.


    🦛