如何折叠同名的文件夹和子文件夹?

如何折叠同名的文件夹和子文件夹?

我有一个巨大的文件档案,我把它从 sftp 下载时搞砸了,而且文件夹结构不正确。

结果是,许多情况下文件夹的级别比应有的级别要深一级(想象一下搞砸了 rsync 命令)。

例如:

/foo/bar/bar
/foo/bar/quux/quux
/foo/bar/baz/quux/quux

也就是说,额外的文件夹不一定总是位于从根目录向下相同数量的文件夹中。它很可能总是一个叶子目录,并且其直接父目录具有相同的名称。

是否有一个很好的脚本方式(bash,Powershell甚至cmd)来递归处理文件夹,类似于这样的伪代码:

  let leafFolders = findLeafFoldersSomehow();  // an array of fully qualified paths

  for (let folder of leafFolders) {
  
    if ( getParentFolderName(folder) == getName(folder) ) {
       // move all files and folders in folder into parentFolder
       // delete folder if empty
    }
  }

我目前正在使用 Windows 批处理文件和 robocopy 的组合来处理这些问题,但这实际上只适用于同一级别的重复项,而且我每次都必须手动运行它。

我真的希望有一种安全的自动方式来“折叠”重复的文件夹名称。我非常确信,文件应该有一个同名的文件夹和子文件夹。我还确信这只会影响叶文件夹及其父文件夹,并且没有什么比/foo/bar/bar/baz/quux

请注意,我无法再次使用正确的 rsync/lftp 参数重新下载档案;文件超过 500GB,我无法再访问服务器。

有没有相对简单的方法可以通过脚本或类似 rsync 的方法来实现这一点?我甚至可以用 node.js 或 C# 编写一些内容,但我宁愿避免这样做,而选择使用 bash、Powershell 甚至 cmd。

我使用的是 Windows 10,但虽然我不是 Linux 专家,但如果需要的话,我可以使用带有 bash 的 WSL。

答案1

您可以创建 bash 脚本并将 workdir 作为参数传递

sudo -H bash /path/to/myscript.sh /foo/bar

find -print0带有 NUL 分隔目录名的 GNU 脚本

#!/bin/bash

# default workdir
[ -z "$1" ] && set -- "$PWD"

find -L "$@" -depth -mindepth 1 -type d -links 2 -print0 | \
while IFS= read -r -d $'\0' dir
  do
    dir=$(realpath "$dir")
    par="${dir%/*}"
    [ -d "$dir" ] || continue
    if [ "${par##*/}" = "${dir##*/}" ]
      then
        cp -aflPx --backup=t "$dir" "${par%/*}"
        find "$dir" -delete
    fi
done

每片叶子传递两次。第一关是从叶子到父级的硬链接文件

  • cp -l用来替代mv
  • cp --backup=t将重命名现有文件

第二遍将清理 leaf。由于文件是硬链接的,因此只删除旧文件路径,而保留 inode 和文件内容(包括所有元数据)

  • find -delete用来替代rm -rf

注意-depth标志将从叶子开始。每个叶子都会进行第一遍和第二遍处理。

[ -d "$dir" ] || continuefind将跳过不存在的目录。这只是为了在无法识别更改的情况下进行复查


编辑:使用此技巧,只有叶子可以移动(Btrfs 除外) thx @ Gohu

相关内容