我有一个巨大的文件档案,我把它从 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" ] || continue
find
将跳过不存在的目录。这只是为了在无法识别更改的情况下进行复查
编辑:使用此技巧,只有叶子可以移动(Btrfs 除外) thx @ Gohu
find -links 2
仅匹配叶目录
如何仅在没有子目录的目录上运行 bash 脚本