Renaming files

There’s been a lot of talk about Name Mangler today, both because a new version just came out (at a temporarily reduced price) and because some guy has been comically threatening1 Many Tricks with legal action.

Name Mangler is a file renaming application. and it looks both powerful and easy to use, but I think I’ll stick to my old command line tools. For photos, I rename the generic IMG_nnnn files that come out of my camera with the date and sequence number using my canonize program. For other files, I use a rename program whose fundamental logic was stolen from the classic first edition of Programming Perl.

Programming Perl

That first edition wasn’t very good at teaching you Perl, but it had some great examples, one of which was a little file renamer that allowed you to specify a Perl regex substitution pattern on the command line to specify the find and replace patterns. Starting with that base, I expanded it a bit, and it’s been serving me well for 15 years or more. Here’s the code:

perl:
 1:  #!/usr/bin/perl
 2:  
 3:  use Getopt::Std;
 4:  
 5:  $usage = <<USAGE;
 6:  rename - renames files via a Perl expression. Adapted from Larry Wall's
 7:           program in the first (pink) edition of the Camel book.
 8:  
 9:  usage: rename [options] '<perl expr>' files
10:  options:
11:      -t:   test - show how the renaming would be done, but don't do it
12:      -h:   help - print this message
13:  
14:  The Perl expression is mandatory. A variable, $n, can be used in the Perl
15:  expression to insert a sequence number into the new name. The files can
16:  be listed on the command line, piped in through STDIN, or given in a file,
17:  one per line.
18:  USAGE
19:  
20:  # Process options.
21:  getopts('th', \%opt);
22:  die $usage if ($opt{h});
23:  
24:  # Make sure there's a perl expression.
25:  ($perlexpr = shift) || die $usage;
26:  
27:  # Handle the different ways of getting the file names.
28:  chomp(@ARGV = <STDIN>) unless @ARGV;
29:  
30:  # Set up a one-based file counting variable.
31:  $n = 1;
32:  
33:  # Do the renaming or show how it would be done.
34:  foreach (@ARGV) {
35:    $old = $_;
36:    eval $perlexpr;
37:    die $@ if $@;
38:    if ($opt{t}) {
39:      print "$old => $_\n";
40:    }
41:    else {
42:      rename($old, $_) unless $old eq $_;
43:    }
44:    $n++;
45:  }

This is a pretty dangerous program. The regex you pass into it could get out of control and overwrite your files. This is why I added the -t option; I wanted to be able to test out tricky renames and see the results before going ahead with the renaming. More seriously, the eval on Line 36 allows you to pass in any destructive set of Perl statements you want (or don’t want). This is old-school give-yourself-enough-rope-to-hang-yourself Unix programming.

Although there are some clever options for passing in a list of files to be named through STDIN or as the contents of a file, I typically just pass them in as arguments on the command line via globbing.

For example, to rename a folder of files with all upper-case names, I’d use

rename 'tr/A-Z/a-z/' *

To rename all files with a .jpeg or .JPEG or .JPG extension to the more standard .jpg, I’d use

rename 's/jpe?g$/jpg/i' *

There’s a way to add a sequence number using the $n variable:

rename 's/.*\.pdf$/Drawing-$n.pdf/' *.pdf

That isn’t the best way to handle the sequence number if there are more than nine files. To get leading zeros so alphabetical and numerical order are the same, I’d use

rename 's/.*\.pdf$/sprintf("Drawing-%03d.pdf", $n)/e' *.pdf

which would give the files names like Drawing-001.pdf instead of just Drawing-1.pdf.

Every one of these examples could blow up in my face if I’m not careful, and none of them are safe for all possible combinations of the original file names. But if you’re good with regexes, it’s a pretty handy tool; if not, there’s always Name Mangler.


  1. I’m sure the threat of legal action isn’t funny to Many Tricks, but it’s hard not to laugh at “incrediblebees laywers.” 


9 Responses to “Renaming files”

  1. Seth Brown says:

    Unix has a tool very similar to what you created. It’s also called rename and works similar to your script:

    rename 's/IMG/FOO/' IMG*

    It isn’t on OS X by default, but it’s available via homebrew. Before I discovered rename, I used the builtin rename in zsh:

    http://www.drbunsen.org/zsh-file-renaming.html

  2. Seth Brown says:

    I just looked at the source code for Unix rename. It’s also written in Perl and looks somewhat similar to your script only with a few more bells and whistles. They must have come from the same original source?

  3. Lauri Ranta says:

    As expected from the elderly OS X blogging crew: http://www.leancrew.com/all-this/2011/09/renaming-with-larry-wall/.

    I’m currently using shell commands like this:

    for f in *; do mv "$f" "${f%.jpeg}.jpg"; done
    for f in *; do mv "$f" "${f,,}"; done
    i=0; for f in *.pdf; do ((i++)); mv "$f" Drawing-$(printf %03d $i).pdf; done
    for f in *; do mv "$f" "${f// /_}"; done
    for f in *.mp3; do mv "$f" "$(printf %02d ${f%% *}) ${f#* }"; done
    IFS=$'\n'; i=1; for f in $(ls -rt); do mv "$f" "$((i++)) $f"; done
    

    I’m trying to do as much as I can with basic shell utilities in order to get faster at using them and to learn more about them. For example I have found a practical use for ${var,,} (a variable substitution for converting to lowercase in Bash 4) and learned that $(printf %03d $((i++))) doesn’t work.

  4. Dr. Drang says:

    I don’t mind repeating myself occasionally (from what I can tell, repetition is the soul of blogging), but it would be nice if I knew when I was doing it. In my defense, Lauri, I’ve been woozy from NyQuil the past couple of days.

  5. Aristotle Pagaltzis says:

    Indeed these scripts come from the same original source, Seth. The rename you found in Homebrew is my version of it, and once I steal the $n feature – a lovely idea! –, Dr. Drang may (or may not, of course) find himself preferring it.

    I have added an array of switches, most of which essentially serve as macros for snippets of Perl code commonly used in renaming files. My grand plan for someday is to add loading of user-defined snippets from ~/.renamerc. For now, there are some more advanced examples in the cookbook section of the docs. The repository is on GitHub and I’ll be grateful for any other useful ideas.

  6. Aristotle Pagaltzis says:

    Although on second look I have to say the current tutorial-style section misses the use of several switches I have added. I’ll have to revise that…

  7. Dr. Drang says:

    I’m afraid, Aristotle, that a guy who doesn’t know when he’s rewritten his own post will never be able to handle 28 (!) command-line options. But if you come up with a better way to get leading zeros with $n than my clumsy sprintf solution, I’ll make the switch.

  8. Aristotle Pagaltzis says:

    Certainly. I just pushed a version which implements an approach to this that I like. If you wouldn’t mind, please take a look and tell me how that looks to you.

  9. Wes Campaigne says:

    There’s also the File::Rename module on CPAN, which is another evolution of Larry Wall’s original example.