Recipe 9.9 Renaming Files

9.9.1 Problem

You have many files whose names you want to change.

9.9.2 Solution

Use a foreach loop and the rename function:

foreach $file (@NAMES) {
    my $newname = $file;
    # change $newname
    rename($file, $newname) or  
        warn "Couldn't rename $file to $newname: $!\n";

9.9.3 Discussion

This is straightforward. rename takes two arguments. The first is the filename to change, and the second is its new name. Perl's rename is a frontend to the operating system's rename syscall, which typically won't rename files across filesystem boundaries.

A small change turns this into a generic rename script, such as the one by Larry Wall shown in Example 9-5.

Example 9-5. rename
  #!/usr/bin/perl -w
  # rename - Larry's filename fixer
  $op = shift or die "Usage: rename expr [files]\n";
  chomp(@ARGV = <STDIN>) unless @ARGV;
  for (@ARGV) {
      $was = $_;
      eval $op;
      die $@ if $@;
      rename($was,$_) unless $was eq $_;

This script's first argument is Perl code that alters the filename (stored in $_) to reflect how you want the file renamed. It can do this because it uses an eval to do the hard work. It also skips rename calls when the filename is untouched. This lets you simply use wildcards like rename EXPR * instead of making long lists of filenames.

Here are five examples of calling the rename program from the shell:

% rename 's/\.orig$//'  *.orig
% rename "tr/A-Z/a-z/ unless /^Make/"  *
% rename '$_ .= ".bad"'  *.f
% rename 'print "$_: "; s/foo/bar/ if <STDIN> =~ /^y/i'  *
% find /tmp -name "*~" -print | rename 's/^(.+)~$/.#$1/'

The first shell command removes a trailing ".orig" from each filename.

The second converts uppercase to lowercase. Because a translation is used rather than the lc function, this conversion won't be locale-aware. To fix that, you'd have to write:

% rename 'use locale; $_ = lc($_) unless /^Make/' *

The third appends ".bad" to each Fortran file ending in ".f", something many of us have wanted to do for a long time.

The fourth prompts the user for the change. Each file's name is printed to standard output and a response read from standard input. If the user types something starting with a "y" or "Y", any "foo" in the filename is changed to "bar".

The fifth uses find to locate files in /tmp that end with a tilde. It renames these so that instead of ending with a tilde, they start with a dot and a pound sign. In effect, this switches between two common conventions for backup files.

The rename script exemplifies the powerful Unix tool-and-filter philosophy. Even though we could have created a dedicated command for lowercase conversion, it's nearly as easy to write a flexible, reusable tool by embedding an eval. By reading filenames from standard input, we don't have to build in the recursive directory walk. Instead, we just use find, which performs this function well. There's no reason to recreate the wheel, although using File::Find we could have.

9.9.4 See Also

The rename function in perlfunc(1) and in Chapter 29 of Programming Perl; your system's mv(1) and rename(2) manpages; the documentation for the standard File::Find module (also in Chapter 32 of Programming Perl)