Flatten files in a folder hierarchy

Have you ever had a huge complicated folder tree with thousands of files buried layers deep in folders-within-folders?

And wanted to flatten them out so all the files are in a single folder, but without filename collisions (that is, lots of files with the same name)?

I did. The closest thing I could find off-the-shelf was this, so I wrote the Python script below.

I hope it’s helpful to someone.

To run it (assuming you have Python installed – you’ll need Python 3 in case some of your pathnames have Unicode characters), do this:

python flatten.py [root folder to be flattened] DOIT

If you leave off “DOIT” (all caps), then it will simulate what it’s going to do, without actually doing anything.

The way it works is by copying all the files to the root folder, renaming them with the original path to the file, but substituting “¦” for the “/” or “\” (Windows, Unix respectively).

So if you have (using the example from the link above) this inside “Folder0”:

Folder0 
          Folder1 
                    File1.1.txt 
                    File1.2.txt 
                    FolderA 
                              FileA.txt 
                    FolderB 
                              FileB.1.txt 
                              FileB.2.txt 
          Folder2 
                    FolderC 
                              FileC.txt

Then doing:

python flatten.py c:\path\to\Folder0 DOIT

Gets you these six files inside Folder0:

Folder1¦File1.1.txt
Folder1¦File1.2.txt
Folder1¦FolderA¦FileA.txt
Folder1¦FolderB¦FileB.1.txt
Folder1¦FolderB¦FileB.2.txt
Folder2¦FolderC¦FileC.txt

Enjoy, and if you make improvements, please post a comment here.

# -*- coding: utf-8 -*-
# for Python3 (needs Unicode)

import os, shutil, sys

def splitPath(p):
    """Returns a list of folder names in path to p

    From user1556435 at http://stackoverflow.com/questions/3167154/how-to-split-a-dos-path-into-its-components-in-python"""
    a,b = os.path.split(p)
    return (splitPath(a) if len(a) and len(b) else []) + [b]

def safeprint(s):
    """This is needed to prevent the Windows console (command line) from choking on Unicode characters in paths.
    
    From Giampaolo Rodolà at http://stackoverflow.com/questions/5419/python-unicode-and-the-windows-console"""
    try:
        print(s)
    except UnicodeEncodeError:
        if sys.version_info >= (3,):
            print(s.encode('utf8').decode(sys.stdout.encoding))
        else:
            print(s.encode('utf8'))

def flatten(root, doit):
    """Flattens a directory by moving all nested files up to the root and renaming uniquely based on the original path.

    Converts all occurances of "SEP" to "REPL" in names (this allows un-flatting later, but at the cost of the replacement).
    
    If doit is True, does action; otherwise just simulates.
    
    """
    
    SEP  = "¦"
    REPL = "?"

    folderCount = 0
    fileCount = 0

    if not doit:
        print("Simulating:")

    for path, dirs, files in os.walk(root, topdown=False):

        if path != root:

            for f in files:

                sp = splitPath(path)

                np = ""

                for element in sp[1:]:
                    e2 = element.replace(SEP, REPL)
                    np += e2 + SEP

                f2 = f.replace(SEP, REPL)
                newName = np + f2

                safeprint("Moved:   "+ newName )
                if doit:
                    shutil.move(os.path.join(path, f), os.path.join(root, newName))
                fileCount += 1

            safeprint("Removed: "+ path)
            if doit:
                os.rmdir(path)
            folderCount += 1

    if doit:
        print("Done.")        
    else:
        print("Simulation complete.")


    print("Moved files:", fileCount)
    print("Removed folders:", folderCount)

"""

if __name__ == "__main__":
    print("")

    print("Flatten v1.00 (c) 2014 Nerdfever.com")
    print("Use and modification permitted without limit; credit to NerdFever.com requested.")

    if len(sys.argv) < 2:
        print("Flattens all files in a path recursively, moving them all to the")
        print("root folder, renaming based on the path to the original folder.")
        print("Removes all now-empty subfolders of the given path.")
        print("")
        print("Syntax: flatten [path] [DOIT]")
        print("")
        print("The DOIT parameter makes flatten do the action; without it the action is only simualted.")
        print("Examples:")
        print("  flatten //machine/path/foo          Simulates flattening all contents of the given path")
        print("  flatten //machine/path/bar DOIT     Actually flattens given path")
    else:
        if len(sys.argv) == 3 and sys.argv[2] == "DOIT":
            flatten(sys.argv[1], True)
        else:
            flatten(sys.argv[1], False)

Leave a Reply

Your email address will not be published. Required fields are marked *