Walk a folder structure, rename files to match sub-folder, keep file extension

Viewed 1008

I am new to python and coding, as a first project, I am looking to create a python script which will traverse a folder structure and look inside each sub-folder and rename the files within to match the name of the sub-folder.

Example folder structure:

root folder
├── first sub-folder (Batman)
│   ├── filename.extension (joker.pdf)
│   └── filename.differentExtension (bane.jpg)
│
└── second sub-folder
    ├── filename.extension
    └── filename.differentExtension

So after the script has completed, the result would look like the below:

root folder
├── first sub-folder (Batman)
│   ├── filename.extension (Batman.pdf)
│   └── filename.differentExtension (Batman.jpg)
│
└── second sub-folder
    ├── filename.extension
    └── filename.differentExtension

I am looking for recommendations into how to approach this, I was thinking of using os.path.splitext to seperate the filename from the file extension, but i'm unsure how to change the filename to the sub-folder value and then concatenate the filename and file extension back together.

4 Answers

I would use os.walk, and ntpath with a recursive function, to walk all directories and subdirectories...renaming as you go. The following code can absolutely be compressed...but I wanted to make it more explicit to help your understanding of what's happening.

import os
import ntpath

print(os.getcwd()) # Information for testing
rootDir = "/home/user/eclipse-workspace/test" # Will start here

# Create a recursive function. 
# This function will call itself, to pass in new directories it finds
def walkit(rootDir):
    # os.walk gives you the name of the directory it's in, 
    # all the subdirs (as a list), 
    # and all the filesnames (as a list)
    for thisDir, thisDir_subDirs, thisDir_files in os.walk(rootDir):
        print("---------------------------")
        print("This Directory =", thisDir, "\nSubdirectories:", thisDir_subDirs, "\nFiles:", thisDir_files)
        # Loop through and change the filenames first
        for filename in thisDir_files:
            # Get JUST the filename with extension
            basename        = ntpath.basename(filename)
            # Split the filename into name + extension 
            name, ext   = os.path.splitext(basename) 
            # Next split the last directory off the full directory path. 
            # Returns a tuple, so you have to take the second element "[1]"
            newname = ntpath.split(thisDir)[1]
            # Python join to create a new name + extension
            newfilename = ''.join([newname, ext])
            # Set the source by joining the current FULL directory path and the FULL old name
            src = os.path.join(thisDir, filename)
            # Set the destination by joining the current FULL path plus the FULL NEW name
            dst = os.path.join(thisDir, newfilename)
            try: # Use try except to catch problems
                print("Renaming '{}' to '{}' ... ".format(src, dst), end = '') 
                os.rename(src, dst) # Rename the file
                print("OK") # Worked
            except Exception as e: # Failed
                print("FAILED! (ERROR:{})".format(str(e)))

        # Now cycle through the list of subdirectories and 
        # send each directory back into the walkit function. 
        # As each loop calls the function, it winds itself a new level deep
        # Each time the function finishes, it "UNwinds" itself one level        
        for rootDir in thisDir_subDirs:
            walkit(rootDir)

# Start the first iteratin by calling the function with the first directory to be parsed
walkit(rootDir)

OUTPUT:

/home/user/eclipse-workspace/
---------------------------
This Directory = /home/user/eclipse-workspace/test 
Subdirectories: ['robin', 'batman'] 
Files: []
---------------------------
This Directory = /home/user/eclipse-workspace/test/robin 
Subdirectories: [] 
Files: ['riddler.jpg', 'penguin.pdf']
ext= .jpg
newfilename =  robin.jpg
Renaming '/home/user/eclipse-workspace/test/robin/riddler.jpg' to '/home/user/eclipse-workspace/test/robin/robin.jpg' ... OK
ext= .pdf
newfilename =  robin.pdf
Renaming '/home/user/eclipse-workspace/test/robin/penguin.pdf' to '/home/user/eclipse-workspace/test/robin/robin.pdf' ... OK
---------------------------
This Directory = /home/user/eclipse-workspace/test/batman 
Subdirectories: [] 
Files: ['joker.pdf', 'bane.jpg']
ext= .pdf
newfilename =  batman.pdf
Renaming '/home/user/eclipse-workspace/test/batman/joker.pdf' to '/home/user/eclipse-workspace/test/batman/batman.pdf' ... OK
ext= .jpg
newfilename =  batman.jpg
Renaming '/home/user/eclipse-workspace/test/batman/bane.jpg' to '/home/user/eclipse-workspace/test/batman/batman.jpg' ... OK

pathlib is a modern module that aggregates nearly all of the path related tools in Python, which are spread out across os.path, os, ntpath, etc. See a table of comparison here.

CAUTION: You should always take great care when using destructive I/O operations such as renaming and deleting files. Know what your code is doing and test it first in isolation to prevent mistaken loss of files. Apply code from any answers here at your own risk.

Given

import pathlib

A directory of subfolders and files in test_dc/:

enter image description here

Code

def rename_files(target_dirpath: str, ignore_pattern: str="^*"):
    """Return a dict and rename files."""
    root_dirpath = pathlib.Path(target_dirpath).expanduser()
    renamed_paths = {}

    for path in root_dirpath.rglob("*"):

        if not path.is_file() or path.match(ignore_pattern):
            continue
        
        # Parse new filename
        dirpath = path.parent
        suffix = path.suffix
        dirname = dirpath.stem
        filename = dirname + suffix
        filepath = dirpath / filename
        
        # Rename
        renamed_paths[str(path)] = filepath.name
        path.rename(filepath)
    return renamed_paths

Demo

print(rename_files("~/test_dc/"))
#{
#     'home/user/test_dc/batman/bane.jpg': 'batman.jpg',
#     'home/user/test_dc/batman/joker.txt': 'batman.txt',
#     'home/user/test_dc/robin/catwoman.png': 'robin.png',
#     'home/user/test_dc/robin/penguin.txt': 'robin.txt'
#}

enter image description here


Details

The goal is to rename a file with the name of its containing sub-folder. Here we recursively traverse a folder to get file paths. Since the sub-folders are part of the path, we extract the sub-folder name, make a new filepath and rename the path.

See also some of the tools used in this demo:

you approach is correct.

root = "root path"
names = []
paths = []
for d in os.listdir(root):
    names.append(d)
    paths.append(os.path.join(root, d))
for i in range(len(names)):
    for j in os.listdir(paths[i]):
        ext = os.path.splitext(j)[-1]
        os.rename(os.path.join(paths[i], j), os.path.join(paths[i], (names[i]+str(ext))))

Another small method should work well when there is 2 level directory as stated in your example.

root_folder = "root folder\\"                                    #root folder
for sub_folder in os.listdir(root_folder):                       #get subfolders
    path = os.path.join(root_folder ,sub_folder)
    for file in os.listdir(path):                                #get files in subfolder
        file_name = file[:file.rfind(".")]                       #get filename
        extension = file[file.rfind("."):]                       #get extension of file
        os.rename(os.path.join(path,file),os.path.join(path,"{}{}".format(sub_folder,extension))) #rename
Related