Recipe 23.6. Renaming Files in Bulk
Problem
You want to rename a bunch of files programmatically: for instance, to normalize the filename case or to change the extensions.
Solution
Use the Find module in the Ruby standard library. Here's a method that renames files according to the results of a code block. It returns a list of files it couldn't rename, because their proposed new name already existed:
require 'find'
module Find
def rename(*paths)
unrenamable = []
find(*paths) do |file|
next unless File.file? file # Skip directories, etc.
path, name = File.split(file)
new_name = yield name
if new_name and new_name != name
new_path = File.join(path, new_name)
if File.exists? new_path
unrenamable << file
else
puts "
Renaming #{file} to #{new_path}" if $DEBUG
File.rename(file, new_path)
end
end
end
return unrenamable
end
module_function(:rename)
end
This addition to the Find module makes it easy to do things like convert all filenames to lowercase. I'll create some dummy files to demonstrate:
require 'fileutils'
tmp_dir = 'tmp_files'
Dir.mkdir(tmp_dir)
['CamelCase.rb', 'OLDFILE.TXT', 'OldFile.txt'].each do |f|
FileUtils.touch(File.join(tmp_dir, f))
end
tmp_dir = File.join(tmp_dir, 'subdir')
Dir.mkdir(tmp_dir)
['i_am_SHOUTING', 'I_AM_SHOUTING'].each do |f|
FileUtils.touch(File.join(tmp_dir, f))
end
Now let's convert these filenames to lowercase:
$DEBUG = true
Find.rename('./') { |file| file.downcase }
#
Renaming ./tmp_files/subdir/I_AM_SHOUTING to ./tmp_files/subdir/i_am_shouting
# Renaming ./tmp_files/OldFile.txt to ./tmp_files/oldfile.txt
# Renaming ./tmp_files/CamelCase.rb to ./tmp_files/camelcase.rb
# => ["./OldFile.txt", "./dir/i_am_SHOUTING"]
Two of the files couldn't be renamed, because oldfile.txt and subdir/i_am_shouting were already taken.
Let's add a ".txt" extension to all files that have no extension:
Find.rename('./') { |file| file + '.txt' unless file.index('.') }
#
Renaming ./tmp_files/subdir/i_am_shouting to ./tmp_files/subdir/i_am_shouting.txt
#
Renaming ./tmp_files/subdir/i_am_SHOUTING to ./tmp_files/subdir/i_am_SHOUTING.txt #
# => []
Discussion
Renaming files in bulk is a very common operation, but there's no standard command-line application to do it because renaming operations are best described algorithmically.
The Find.rename method makes several simplifying assumptions. It assumes that you want to rename regular files and not directories. It assumes that you can decide on a new name for a file based solely on its filename, not on its full path. It assumes that you'll handle in some other way the files it couldn't rename.
Another implementation might make different assumptions: it might yield both path and name, and use autoversioning to guarantee that it can rename every file, although not necessary to the exact filename returned by the code block. It all depends on your needs.
Perhaps the most common renaming operation is modifying the extensions of files. Here's a method that uses Find.rename to make this kind of operation easier:
module Find
def change_extensions(extension_mappings, *paths)
rename(*paths) do |file|
base, extension = file.split(/(.*)\./)[1..2]
new_extension = extension
extension_mappings.each do |re, ext|
if re.match(extension)
new_extension = ext
break
end
end
"#{base}.#{new_extension}"
end
end
module_function(:change_extensions)
end
This code uses Find.change_extensions to normalize a collection of images. All JPEG files will be given the extension ".jpg", all PNG files the extension ".png", and all GIF files the extension ".gif".
Again, we'll create some dummy image files to test:
tmp_dir = 'tmp_graphics'
Dir.mkdir(tmp_dir)
['my.house.jpeg', 'Construction.Gif', 'DSC1001.JPG', '52.PNG'].each do |f|
FileUtils.touch(File.join(tmp_dir, f))
end
Now, let's rename:
Find.change_extensions({/jpe?g/i => 'jpg',
/png/i => 'png',
/gif/i => 'gif'}, tmp_dir)
#
Renaming tmp_graphics/52.PNG to tmp_graphics/52.png
#
Renaming tmp_graphics/DSC1001.JPG to tmp_graphics/DSC1001.jpg
# Renaming tmp_graphics/Construction.Gif to tmp_graphics/Construction.gif
# Renaming tmp_graphics/my.house.jpeg to tmp_graphics/my.house.jpg
See Also
Some Unix installations come with a program or Perl script called rename, which can do your renaming if you can represent it as a string substitution or a regular expression; you may not need anything else Recipe 6.14, "Backing Up to Versioned Filenames" Recipe 6.20, "Finding the Files You Want"
 |