Systematic filename manipulation using ruby and regular expressions

Sometimes, I need to rename many files in a systematic way (either strip off some part of the filename or replace it with another string).
Although you can solve that problem using a shell scripting language and sed there is a lot of potential to mistype the sed command
resulting in data loss.

An obvious choice would have been to use perl, but I decided to give it a try with ruby.
Here is the result, may this can serve as a simple example (and hopefully not a good example of a bad example :) )

#!/usr/bin/ruby

# Project:      rename files
# Date:         Thu Sep 21 13:02:57 CEST 2006
# Author:       Nicholas Stallard
# Description:  change parts of a filename to something else

# get the command line parameters
require 'getoptlong'

# this is for the cvs
cvsversion  = "$Revision: 1.9 $"
version     = cvsversion.split.slice(1)

# program information
progname    = File.basename($0,".rb")
proginfo    = progname+" "+version

# parameter defaults
dofiles     = FALSE
from        = ""
to          = ""

# get the options
opts = GetoptLong.new(
    [ "--help",         "-h",   GetoptLong::NO_ARGUMENT ],
    [ "--onlyfiles",    "-o",   GetoptLong::NO_ARGUMENT ],
    [ "--from",         "-f",   GetoptLong::REQUIRED_ARGUMENT ],
    [ "--to",           "-t",   GetoptLong::REQUIRED_ARGUMENT ],
    [ "--uppercase",    "-u",   GetoptLong::REQUIRED_ARGUMENT ],
    [ "--lowercase",    "-l",   GetoptLong::REQUIRED_ARGUMENT ],
    [ "--capitalize",   "-c",   GetoptLong::REQUIRED_ARGUMENT ],
    [ "--remove",       "-r",   GetoptLong::REQUIRED_ARGUMENT ]
)

# clean exit upon control-c
trap("INT") { exit 1 }

# show some help
def printusage (info, name)
    puts info
    puts
    puts "Usage:"
    puts
    puts name+" [--onlyfiles] --from=<string> --to=<replacement string>"
    puts
    puts "Shortcuts:"
    puts "  --uppercase=<string>"
    puts "  --lowercase=<string>"
    puts "  --captialize=<string>"
    puts "  --remove=<string>"
    puts
    puts "The string transformation is applied to all items in the current directory"
    puts "unless the option --onlyfiles is supplied, where only file names are manipulated"
    puts
end


# if no argument was supplied, also display help :-)
if (ARGV.empty?)
    printusage(proginfo, progname)
    exit
end

# get the arguments
opts.each do |opt, arg|
    case opt
        when '--help'
            printusage(proginfo, progname)
            exit
        when '--from'
            from        = arg
        when '--to'
            to          = arg
        when '--uppercase'
            from        = arg
            to          = from.upcase
        when '--lowercase'
            from        = arg
            to          = from.downcase
        when '--remove'
            from        = arg
            to          = ""
        when '--capitalize'
            from        = arg
            to          = from.capitalize
        when '--onlyfiles'
            dofiles     = TRUE
    end
end

# there is a bug, some say it may be a feature :-)
# supplying --from without --to works like --remove

# set up an array containing all the items in the current directory
# matching the regular expression
filelist            = Dir.glob("*")

# only rename files, omit directories
if dofiles
    newfiles = Array.new
    filelist.each do |d|
        if File.file?(d)
            newfiles.push(d)
        end
    end
    filelist = newfiles
end

# And process each element within the array
filelist.each do |d|

    # replace one string with another
    nfn = d.gsub(/#{from}/, "#{to}")

    # this will empty a string if it is full of whitespaces
    nfn.strip!

    # avoid mayhem and disallow empty names
    if (nfn.empty?)
        puts "Error: Resulting name would be empty!"
        exit 1
    end

    # No need to rename if old and new name are identical
    if ( d == nfn )
        puts "Warning: Not renaming "+d+" (New name is identical to old one)"
        next
    end

    # do not overwrite existing items
    if ( File.file?(nfn) )
        puts "Error: Cowardly refusing to rename "+d+" ("+nfn+" exists!)"
        next
    end

    # and proceed with the rename operation
    File.rename(d, nfn)
    puts "Renamed "+d+" to "+nfn

end
exit 0

Leave a comment