Welcome, guest | Sign In | My Account | Store | Cart

This script will swap extensions on all files in the specified directory, and all of its subdirectories, and all of their subdirectories, etc. Define your arguments with care. Swapextensions.py will do what you tell it to do!

Python, 23 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# documentation is at http://www.outwardlynormal.com/python/swapextensions.htm
# send in 3 strings: directory path,  "before" extension, "after" extension

import os

def swapextensions(dir, before, after):
    if before[:1]!='.': before = '.'+before
    if after[:1]!='.': after = '.'+after
    os.path.walk(dir, callback, (before, -len(before), after))

def callback((before, thelen, after), dir, files):
    for oldname in files:
        if oldname[thelen:]==before:
            oldfile = os.path.join(dir, oldname)
            newfile = oldfile[:thelen] + after
            os.rename(oldfile, newfile)

if __name__=='__main__':
    import sys
    if len(sys.argv) != 4:
        print "Usage: swapext rootdir before after"
        sys.exit(100)
    swapextensions(sys.argv[1], sys.argv[2], sys.argv[3])

This is useful for changing the extensions of a whole batch of files in a folder structure, e.g. a web site. You can also use it for correcting errors made when saving a batch of files programatically.

5 comments

Julius Welby (author) 19 years, 6 months ago  # | flag

A tiny change. I just removed a redundant line of code.

In use, look out for escaped characters if your OS uses backslash characters in folder paths. Using a raw string to define the "dir" argument might be the best approach.

Julius Welby (author) 19 years, 4 months ago  # | flag

A new, improved, swapextensions.py. This script made it into the Cookbook book. The editors gave it a good going over, so I'm reposting it.

Changes:

The "checking for leading dots" is now in the swapextensions function, not in the call back. This now only gets done once, which is as it should be.

The script now explicitly only affects the end of the file name, so files that happen to have ."extension" inside their name as well as at the end will only get the real extension changed. This makes the script more targeted, and so more safe.

The redundant import of string survived the editing process. I've removed it here.

Drew Perttula 19 years, 4 months ago  # | flag

this is not for shell users. I love python as much as the next cookbook reader, but I'd like to point out that this is not a useful python script. The reason is the same as why we don't have a recipe for "copy files with a given extension into another directory"[1] or "create the given empty directory trees"[2]. Simple file renaming operations are easily done in the shell.

I use zsh, where I say:

for x (**/*.ext1) { mv $x $x:r.ext2 }

The shell variable x loops over all files that match .ext1 in all directories below the current one (* is short for something like "any number of subdirectories"). $x:r is the "root" of $x; $x without its extension. When $x is "foo/file1.ext1", the loop body becomes "mv foo/file1.ext1 foo/file1.ext2". :r is not the only zsh modifier by any means. You can do incredible things with just a few keystrokes.[3]

I guess maybe windows users might need extra tools to manipulate their filenames, but I would think they'd just download and run zsh if they cared about this kind of thing.

(Yes, I know my example's functionality is slightly different for cases like file.ext1.ext2.ext3. I don't see the common-case usefulness of the python script's different behavior, though.)

[1] cp *.ext newdir

[2] mkdir -p path1/part2/part3 path2/part4

[3] more modifiers at http://zsh.sourceforge.net/Doc/Release/zsh_13.html#SEC45

Frank Jacques 19 years, 4 months ago  # | flag

Minor comments. Perhaps

if before[:1] != '.'

could be wrote

if before[0] != '.'

It looks clearer for me...

Also, passing the negative length of 'before' to the 'callback' function and use it for slicing is pretty disturbing...

What about passing the (positive) length of the var 'before' and negate it in the slice, it should make it explicit if the slice value is referenced from the start of the string (positive values) or from the end of the string (negative value). It's no very explicit here.

Regards

Heather Brysiewicz 13 years, 6 months ago  # | flag

The zsh line was much more helpful and a much easier solution to my problem than a python script! Thanks!