Oli Warner About Contact Oli on Twitter Subscribe

Bulk renaming files in Ubuntu; the briefest of introductions to the rename command

Friday, 31 October 2014 bash rename ubuntu

I’ve seen more than a few Ask Ubuntu users struggling with how to batch rename their files. They get lost in Bash and find -exec loops and generally make a big mess of things before asking for help. But there is an easy method in Ubuntu that relatively few users know about: the rename command.

While I seemingly use it at least once a week now, it took me a couple of years to discover the rename command. And now I honestly can’t remember how I survived without it. Let’s just spend a second or two marvelling at the outward simplicity of syntax and we’ll crack on.

rename [-v] [-n] [-f] perlexpr [filenames]

Let’s talk about those first two flags quickly. -v will make it tell you what it’s doing and -n will make it exit before it actually does anything. If you are in any doubt about your syntax, sling -vn on the end to test. -f will give permission for rename to overwrite files. Be careful.

Replacing (and adding and removing) with s/.../.../

This is probably the most common usage for rename. You’ve got a bunch of files that have the wrong junk in their filenames in them, or you want to change the formatting, or add a prefix, or replace certain characters… rename lets us do all this through simple regular expressions.

I’m using -vn here so the changes aren’t persisting. Let’s start by creating a few files:

$ touch dog{1..3}.dog
$ ls
dog1.dog dog2.dog dog3.dog

Replacing the first dog with cat:

$ rename 's/dog/cat/' * -vn
dog1.dog renamed as cat1.dog
dog2.dog renamed as cat2.dog
dog3.dog renamed as cat3.dog

Replacing the last dog with cat (note $ means “end of line” in this context, ^ means start):

$ rename 's/dog$/cat/' * -vn
dog1.dog renamed as dog1.cat
dog2.dog renamed as dog2.cat
dog3.dog renamed as dog3.cat

Replacing all instances of dog with the /g (global) flag:

$ rename 's/dog/cat/g' * -vn
dog1.dog renamed as cat1.cat
dog2.dog renamed as cat2.cat
dog3.dog renamed as cat3.cat

Removing a string is as simple as replacing it with nothing. Let’s nuke the first dog:

$ rename 's/dog//' * -vn
dog1.dog renamed as 1.dog
dog2.dog renamed as 2.dog
dog3.dog renamed as 3.dog

Adding strings is a case of finding your insertion point and replacing it with your string. Here’s how to add “PONIES-” to the start:

$ rename 's/^/PONIES-/' * -vn
dog1.dog renamed as PONIES-dog1.dog
dog2.dog renamed as PONIES-dog2.dog
dog3.dog renamed as PONIES-dog3.dog

This is all fairly simple and your ability to use it in the wild is largely going to depend on your ability to manipulate regular expressions. I’ve been using them for well over a decade in a professional setting so this might be something I take for granted, but they aren’t hard once you get past the syntax. Here’s a fairly simple introduction to REGEX if you would like to learn more.

Zero-padding numbers so they sort correctly

ls can be pretty shoddy sorting numbers correctly. Here’s a simple example:

$ touch {1..11}
$ ls
1 10 11 2 3 4 5 6 7 8 9

It’s sorting each character position at a time, left to right. This isn’t too bad when we’re only talking about tens, but this scales up and you end up with thousands coming before 9s. A good way to fix this (ls isn’t the only application with this issue) is to zero-pad the beginnings of the numbers so all numbers are the same length and their values are in corresponding positions. Rename makes this super-easy because we can dip into Perl and use sprintf to reformat the number:

$ rename 's/\d+/sprintf("%02d", $&)/e' *
$ ls
01 02 03 04 05 06 07 08 09 10 11

The %02d there means we’re printing at least 2 characters and padding it with zeroes if we need to. If you’re dealing with thousands, increase that to 4, 5 or 6.

$ rename 's/\d+/sprintf("%05d", $&)/e' *
$ ls
00001 00002 00003 00004 00005 00006 00007 00008 00009 00010 00011

Similarly, you can parse a number and remove the zero padding with something like this:

$ rename 's/\d+/sprintf("%d", $&)/e' *
$ ls
1 10 11 2 3 4 5 6 7 8 9

Attaching a counter into the filename

Say we have three files and we want to add a counter onto the filename:

$ touch {a..c}
$ rename 's/$/our $i; sprintf("-%02d", 1+$i++)/e' * -vn
a renamed as a-01
b renamed as b-02
c renamed as c-03

It’s the our $i that let’s us persist a variable state over multiple passes.

Incrementing an existing number in a file

Given three files with consecutive numbers, increment them. It’s a simple enough expression but we have to be mindful that sometimes there are going to be conflicting filenames. Here’s an example that moves all the files to temporary filenames and then strips them back to what they should be.

$ touch file{1..3}.ext
$ rename 's/\d+/sprintf("%d-tmp", $& + 1)/e' * -v
file1.ext renamed as file2-tmp.ext
file2.ext renamed as file3-tmp.ext
file3.ext renamed as file4-tmp.ext

$ rename 's/(\d+)-tmp/$1/' * -v
file2-tmp.ext renamed as file2.ext
file3-tmp.ext renamed as file3.ext
file4-tmp.ext renamed as file4.ext

Changing a filename’s case

Until now we’ve been using substitutions and expressions but there are other forms of Perl expression. In this case we can remap all lowercase characters to uppercase with a simple translation:

$ touch lowercase UPPERCASE MixedCase
$ rename 'y/a-z/A-Z/' * -vn
lowercase renamed as LOWERCASE
MixedCase renamed as MIXEDCASE

We could do that with an substitution-expression like: s/[a-z]/uc($&)/ge

Fixing extension based on actual content or MIME

What if you are handed a bunch of files without extensions? Well you could loop through and use things like the file command, or you could just use Perl’s File::MimeInfo::Magic library to parse the file and hand you an extension to tack on.

rename 's/.*/use File::MimeInfo qw(mimetype extensions); $&.".".extensions(mimetype($&))/e' *

This one is a bit of a monster but further highlights that anything you can do in Perl can be done with rename. You could read ID3 tags from music or process internal data to get filename fragments.

Why doesn’t my rename take a perlexpr?!

Ubuntu’s default rename is actually a link to a Perl script called prename. Some distributions ship the util-linux version of rename --called rename.ul in Ubuntu-- instead. You can work out which version you have using the following command:

$ dpkg -S $(readlink -f $(which rename))
perl: /usr/bin/prename

So unless you want to shunt things around, you’ll have to install and call prename instead of rename.