Monday, July 12, 2010

pymv.py - A batch file rename script

Here's a script I wrote to batch rename files, tested on Python 2.5.2, support for "yield" keyword is required.

#!/usr/bin/env python

import os
import re
import shutil

################################################################################
#                                                                              #
# Script to batch rename files using a regular expression.                     #
#                                                                              #
################################################################################

def get_opt():
    from optparse import OptionParser
    parser = OptionParser('usage: %prog -s  (-c|-m) -i in_pattern -o out_pattern [-e regexp]')
    parser.add_option('-v', '--verbose', help='Verbose output.', action='store_true')
    parser.add_option('-t', '--test', help='Test mode.', action='store_true')
    parser.add_option('-c', '--copy', action='store_true',
                      help='Use copy mode, which copies the FROM file into the ' \
                      'TO name. Not compatible with the -m option.')
    parser.add_option('-m', '--move', action='store_true',
                      help='Use move mode, which renameds the FROM to the ' \
                      'TO file. Not compatible with the -c option.')
    parser.add_option('-i', '--inname', 
                      help='A regular expression to match against file names.')
    parser.add_option('-o', '--outname', 
                      help='A replacement pattern for output file name. ' \
                          'The "To" pattern may refer capture groups in the "From" '\
                          'parameter.')
    parser.add_option('-s', '--startdir', 
                      help='Recusively search the specified directory for files to process.')
    parser.add_option('-e', '--extra', 
                      help='An filter pattern to be applied on the full input file name, '\
                          'only files that matches this pattern will be processed.')
    parser.defaults = {
        'verbose' : False,
        'test' : False,
        'copy' : False,
        'move' : False,
        'inname' : None,
        'outname' : None,
        'startdir' : None,
        'extra' : None
        }
    params, args = parser.parse_args()
    def _err(message):
        print message
        params.ok = False
        return params
    params.ok = True
    if not (params.copy or params.move):
        return _err('No mode specified: Please specify either -c (copy) or -m (move).')

    if (params.copy and params.move):
        return _err('Too many modes specified: Does not support -c (copy) AND ' \
                        '-m (move) together.')

    if not (params.inname and params.outname):
        return _err('File name required: Must specify a pattern for input and ' \
                        'output files.')
    
    if not params.startdir:
        return _err('Must specify a starting directory.')

    params.inpattern = re.compile(params.inname)
    if params.extra:
        params.extrapattern = re.compile(params.extra)
    else:
        params.extrapattern = None

    return params

class MatchedFile:
    def __init__(self):
        self.basename = None
        self.filename = None
        self.toname = None

    def __repr__(self):
        return 'b=%s, i=%s, o=%s' % (self.dirname, self.basename, self.toname)

def match_file_or_dir(params, root):
    for elem in os.listdir(root):
        elem = os.path.join(root, elem)
        if os.path.isdir(elem):
            for subelem in match_file_or_dir(params, elem):
                yield subelem
        else:
            dirname = os.path.dirname(elem)
            basename = os.path.basename(elem)
            m = params.inpattern.match(basename)
            file = MatchedFile()
            file.basename = basename
            file.dirname = dirname
            if m:
                file.toname = m.expand(params.outname)
                if not params.extrapattern:
                    yield file
                elif params.extrapattern.match(os.path.join(dirname, basename)):
                    yield file


if __name__ == '__main__':
    params = get_opt()
    if params.ok:
        for elem in match_file_or_dir(params, params.startdir):
            absname = os.path.join(elem.dirname, elem.basename)
            absto = os.path.join(elem.dirname, elem.toname)
            if elem.basename == elem.toname:
                print 'file %s not changed' % (absname)
            elif params.copy:
                if params.verbose:
                    print 'cp %s %s' % (absname, absto)
                if not params.test:
                    shutil.copyfile(absname, absto)
            elif params.move:
                if params.verbose:
                    print 'mv %s %s' % (absname, absto)
                if not params.test:
                    shutil.move(absname, absto)

No comments:

Post a Comment