我有两个具有相同文件的驱动器,但目录结构完全不同。
有没有办法“移动”目标端的所有文件,以便它们与源端的结构匹配?也许有脚本?
例如,驱动器 A 有:
/foo/bar/123.txt
/foo/bar/234.txt
/foo/bar/dir/567.txt
而驱动器 B 具有:
/some/other/path/123.txt
/bar/doo2/wow/234.txt
/bar/doo/567.txt
有问题的文件很大(800GB),所以我不想重新复制它们;我只想通过创建必要的目录并移动文件来同步结构。
我正在考虑一个递归脚本,该脚本将找到目标上的每个源文件,然后将其移动到匹配的目录,并在必要时创建它。但是——这超出了我的能力范围!
这里给出了另一个优雅的解决方案: https://superuser.com/questions/237387/any-way-to-sync-directory-struct-when-the-files-are-already-on-both-sides/238086
答案1
我会和 Gilles 一起去,并按照建议向您指出 Unison哈森杰。 Unison 是 DropBox,比 DropBox 早 20 年。很多人(包括我自己)每天都使用的坚如磐石的代码——非常值得学习。尽管如此,仍然join
需要它能得到的所有宣传:)
这只是答案的一半,但我必须回去工作:)
基本上,我想演示一个鲜为人知的join
实用程序,它的作用就是:在某个字段上连接两个表。
首先,设置一个包含带空格的文件名的测试用例:
for d in a b 'c c'; do mkdir -p "old/$d"; echo $RANDOM > "old/${d}/${d}.txt"; done
cp -r old new
(编辑一些目录和/或文件名new
)。
现在,我们要为每个目录构建一个映射:哈希 -> 文件名,然后用于join
匹配具有相同哈希的文件。要生成地图,请将以下内容放入makemap.sh
:
find "$1" -type f -exec md5 -r "{}" \; \
| sed "s/\([a-z0-9]*\) ${1}\/\(.*\)/\1 \"\2\"/" \
makemap.sh
吐出一个文件,其中的行格式为“哈希“文件名””,因此我们只需加入第一列:
join <(./makemap.sh 'old') <(./makemap.sh 'new') >moves.txt
这会生成moves.txt
如下所示的内容:
49787681dd7fcc685372784915855431 "a/a.txt" "bar/a.txt"
bfdaa3e91029d31610739d552ede0c26 "c c/c c.txt" "c c/c c.txt"
下一步将是实际执行这些动作,但我的尝试陷入了引用......mv -i
并且mkdir -p
应该派上用场。
答案2
有一个名为 unison 的实用程序:
http://www.cis.upenn.edu/~bcpierce/unison/
网站描述:
Unison 是一个适用于 Unix 和 Windows 的文件同步工具。它允许将文件和目录集合的两个副本存储在不同的主机(或同一主机上的不同磁盘)上,分别进行修改,然后通过将每个副本中的更改传播到另一个副本来更新。
请注意,如果至少有一个根目录是远程的,Unison 仅在第一次运行时检测移动的文件,因此即使您正在同步本地文件,也可将其用作ssh://localhost/path/to/dir
其中一个根目录。
答案3
使用齐奏作为由哈森·j建议。我将这个答案作为一个可能有用的脚本示例或在仅安装了基本实用程序的服务器上使用。
我假设文件名在整个层次结构中是唯一的。我还将假设没有文件名包含换行符,并且目录树仅包含目录和常规文件。
首先收集源端的文件名。
(cd /A && find . \! -type d) >A.find
然后将文件移动到目标端的适当位置。首先,在目标端创建一个扁平化的文件树。如果您想在旧层次结构中保留硬链接,请使用
ln
而不是。mv
mkdir /B.staging /B.new find /B.old -type f -exec sh -c 'mv -- "$@" "$0"' /B.staging {} +
如果目标中可能缺少某些文件,请创建类似的扁平化文件
/A.staging
并使用 rsync 将数据从源复制到目标。rsync -au /A.staging/ /B.staging/
现在将文件重命名到位。
cd /B.new && <A.find perl -l -ne ' my $dir = '.'; s!^\./+!!; while (s!^([^/]+)/+!!) { # Create directories as needed $dir .= "/$1"; -d $dir or mkdir $dir or die "mkdir $dir: $!" } rename "/B.staging/$_", "$dir/$_" or die "rename -> $dir/$_: $!" '
等效地:
cd /B.new && <A.find python -c ' import os, sys for path in sys.stdin.read().splitlines(): dir, base = path.rsplit("/", 2) os.rename(os.path.join("/B.new", base), path) '
最后,如果您关心目录的元数据,请使用已就位的文件调用 rsync。
rsync -au /A/ /B.new/
请注意,我尚未测试本文中的片段。使用风险自负。请在评论中报告任何错误。
答案4
特别是如果持续同步有用,您可以尝试弄清楚git 附件。
它相对较新;我自己还没有尝试使用它。
我之所以能够建议它,是因为它避免了保留文件的第二个副本...这意味着它必须将文件标记为只读(“锁定”),就像某些非 Git 版本控制系统一样。
文件由 sha256sum + 文件扩展名标识(默认情况下)。因此,它应该能够同步两个具有相同文件内容但不同文件名的存储库,而无需执行写入(如果需要,还可以通过低带宽网络)。当然,它必须读取所有文件才能对其进行校验和。