重新排列嵌套目录

Rearranging nested directory

我有一个结构如下的文件夹系统:

folderA 
- folder1
  - file1A.txt
- folder2
  - file2A.txt
- folder3
  - file3A.txt

folderB 
- folder1
  - file1B.txt 
- folder2 
  - file2B.txt 
- folder3
  - file3B.txt

我想更改顺序,使字母文件夹上方的编号文件夹为:

folder1 
- folderA
  - file1A.txt
- folderB
  - file1B.txt

folder2 
- folderA
  - file2A.txt 
- folderB 
  - file2B.txt 

folder3 
- folderA
  - file3A.txt 
- folderB 
  - file3B.txt 

这里有一段代码构建了初始目录结构的MWE:

import os 
import shutil
import string

root_dir = os.getcwd()
os.chdir('/home/alletro/Tc-97/tools')
os.makedirs('master', exist_ok=True)
os.chdir('master') 
master_dir = os.getcwd()
top_tier = [f'folder{i}' for i in range(1,4)]
second_tier = [f'folder{i}' for i in list(string.ascii_uppercase)[:4]]
for folder in top_tier: 
    os.chdir(master_dir)
    os.makedirs(folder, exist_ok=True) 
    os.chdir(folder)
    fold_dir = os.getcwd()
    for sub_folder in second_tier:
        os.chdir(fold_dir)
        os.makedirs(sub_folder, exist_ok=True) 
        os.chdir(sub_folder)
        os.mknod("newfile.txt")
os.chdir(root_dir)

我找到了一个解决方案,可以获取目录树的字典:

def get_directory_structure(rootdir):
    """
    Creates a nested dictionary that represents the folder structure of rootdir
    """
    dir = {}
    rootdir = rootdir.rstrip(os.sep)
    start = rootdir.rfind(os.sep) + 1
    for path, dirs, files in os.walk(rootdir):
        folders = path[start:].split(os.sep)
        subdir = dict.fromkeys(files)
        parent = reduce(dict.get, folders[:-1], dir)
        parent[folders[-1]] = subdir
    return dir

然而,我正在努力寻找从这里获取它的位置。

以下对我有用。有四个部分:

  1. 获取所有文件路径的列表。
  2. 使用该列表创建所需子文件夹的列表。
  3. 创建子文件夹并将文件移入其中。
  4. 删除旧的文件层次结构。

第 1 部分: 获取文件路径列表。

import os
import glob
import shutil

all_files = [filename for filename in glob.iglob('**/*.txt', recursive=True)]

第 2 部分:使用文件名创建子文件夹列表。

# Function that takes a filename and returns desired subfolder and file paths as tuple. 
def get_full_path(file):
    num = file[-6]
    let = file[-5]
    file_name = file.split('/')[-1]
    
    subfolders = f'folder{num}/folder{let}/'
    file_path = f'folder{num}/folder{let}/{file_name}'
    return subfolders, file_path

# Call the function on each file to get all subfolders and file paths.
subfolders = []
full_paths = []
for file in all_files:
    subfolder, full_path = get_full_path(file)
    subfolders.append(subfolder), full_paths.append(full_path)

第 3 部分:创建所有子文件夹,将所有文件移入其中。

for i in range(len(all_files)):
    os.makedirs(subfolders[i])
    os.rename(all_files[i], full_paths[i])

第 4 部分:删除原来的文件夹和子文件夹。

old_folders = [x for x in os.listdir() if x[-1] in ['A', 'B', 'C']]
for folder in old_folders:
    shutil.rmtree(folder, ignore_errors=False, onerror=None)

如果您有文件夹 'C' 之外的文件夹,您需要在第 4 部分中扩展 in ['A', 'B', 'C'] 列表。否则这应该任意扩展。

如果您有任何问题,请告诉我,祝项目的其余部分一切顺利!

这是一个不采用特定文件夹名称的解决方案:

from pathlib import Path

def switch_parts(path):
    one, two, three, four = path.parts
    return Path(one, three, two, four)

# generate new paths
files = Path('master').glob('**/*.txt')
rearranged = [switch_parts(f) for f in files]

# create new folders and move files
for old, new in zip(files, rearranged):
    new.parent.mkdir(parents=True, exist_ok=True)
    old.rename(new)

# clean up old folders
for old in files:
    old.parent.rmdir()
    try:
        old.parent.parent.rmdir()
    except OSError:
        pass # will be deleted eventually

你可以先获取所有文件的路径,然后使用递归创建新结构:

import os, shutil, collections
#generator function that finds all file paths in the target directory
def get_files(d = os.getcwd(), p = None):
  for i in os.listdir(d):
     if os.path.isdir(_d:=os.path.join(d, i)):
        yield from get_files(d = _d, p = ([] if p is None else p)+[i])
     else:
        yield ([] if p is None else p)+[i]

#group all reversed file paths from the function above on the leading dir name
def new_structure(d, p = []):
   _dir = collections.defaultdict(list)
   for a, b, c in d:
      if not a:
         shutil.copyfile(c, os.path.join(os.getcwd(), *p, b))
      else:
         _dir[a[0]].append((a[1:], b, c))
   for a, b in _dir.items():
      os.mkdir(os.path.join(os.getcwd(), *(_p:=p+[a])))
      new_structure(b, p = _p)

r = [[a[::-1], b, os.path.join(*a, b)] for *a, b in get_files()]
new_structure(r)
#remove old structure
for (*_, a), _, _ in r:
   if os.path.isdir(a):
      shutil.rmtree(a, ignore_errors=False, onerror=None)

此解决方案适用于任何深度的目录以及完全任意的目录和文件名。