Flatten complex directory structure in Python

Mirzhan Irkegulov picture Mirzhan Irkegulov · Jul 9, 2013 · Viewed 7.6k times · Source

I want to move files from a complex directory structure to just one place. For example i have this deep hierarchy:

foo/
    foo2/
        1.jpg
    2.jpg
    ...

I want it to be:

1.jpg
2.jpg
...

My current solution:

def move(destination):
    for_removal = os.path.join(destination, '\\')
    is_in_parent = lambda x: x.find(for_removal) > -1
    with directory(destination):
        files_to_move = filter(is_in_parent,
                               glob_recursive(path='.'))
    for file in files_to_move:
        shutil.move(file, destination)

Definitions: directory and glob_recursive. Note, that my code only moves files to their common parent directory, not an arbitrary destination.

How can i move all files from a complex hierarchy to a single place succinctly and elegantly?

Answer

Scout picture Scout · Jul 9, 2013

I don't like testing the name of the file about to be moved to see if we're already in the destination directory. Instead, this solution only scans the subdirectories of the destination

import os
import itertools
import shutil


def move(destination):
    all_files = []
    for root, _dirs, files in itertools.islice(os.walk(destination), 1, None):
        for filename in files:
            all_files.append(os.path.join(root, filename))
    for filename in all_files:
        shutil.move(filename, destination)

Explanation: os.walk walks recursively the destination in a "top down" manner. whole filenames are constructed with the os.path.join(root, filename) call. Now, to prevent scanning files at the top of the destination, we just need to ignore the first element of the iteration of os.walk. To do that I use islice(iterator, 1, None). One other more explicit way would be to do this:

def move(destination):
    all_files = []
    first_loop_pass = True
    for root, _dirs, files in os.walk(destination):
        if first_loop_pass:
            first_loop_pass = False
            continue
        for filename in files:
            all_files.append(os.path.join(root, filename))
    for filename in all_files:
        shutil.move(filename, destination)