我正在两台服务器之间复制数亿个小图像的目录结构。复制期间需要保留文件结构、所有权和权限。我们的测试表明,执行此复制的最快方法是将文件打包成 tar 并通过 netcat 传输,命令如下:
# TARGET (extract):
$ nc -l 2222 | pigz -d | sudo tar xpf - --same-owner -C /
# SOURCE:
$ tar -cf - -T selected-images-to-copy.txt | pigz | pv | nc 1.1.1.1 2222
执行复制的其他方法(例如 rsync、scp)太慢了,需要数周才能完成,因为它们不会使网络饱和,而这种方法将在几天内完成。然而,虽然图像本身是在正确的所有权和权限下创建的,但提取正在执行的目录却不是。
如果我不提取 tar,而是查看我拥有的内容:
$ tar tvzf test.tar.gz
-rw-r--r-- root/www-data 319434 2017-09-23 05:47 mnt/a/b/c/0012Z.jpg
-rw-r--r-- root/www-data 323647 2017-09-23 05:47 mnt/a/b/c/0005Z.jpg
-rw-r--r-- root/www-data 315962 2017-09-23 05:47 mnt/a/b/c/0013Z.jpg
-rw-r--r-- root/www-data 313594 2017-09-23 05:47 mnt/a/b/c/0007Z.jpg
但是,提取时,mnt 和图像之间的提取创建的所有文件夹都归 root:root 所有,并具有权限 0750,这意味着除了 root 之外的任何人都无法访问它们。
$ sudo ls -al mnt/a/b
total 12
drwxr-x--- 3 root root 4096 Oct 6 15:01 .
drwxr-x--- 3 root root 4096 Oct 6 15:01 ..
drwxr-x--- 3 root root 4096 Oct 6 15:01 c
由于文件数量众多,chown 和 chmod 等递归操作将需要很长时间才能运行。我们有一个自定义的 python 脚本可以更改权限,但这又会延长进程的时间;所以如果可能的话,我希望立即获得权限。
注意:在研究这个问题时,我发现这服务器故障问题提出了类似的问题,但结论是这是一个在 tar v1.24 中修复的错误。
$ tar --version
tar (GNU tar) 1.27.1
答案1
如果selected-images-to-copy.txt
只是文件列表(路径的最后一个元素始终是文件,而不是目录),则可以使用以下解决方案来创建具有适当目录权限的档案:
编辑:我在最后添加了一个更好的解决方案,同时保留了中间解决方案,利用 dave_thompson_085 的评论并思考可以利用现有信息进行哪些改进。
正如他所写(我没有完全解释),解决方案的重要部分是使用--no-recursion
。这允许保存路径中每个手动添加的目录的所有元信息,直到文件本身,而不包括所有其他不需要的目录和文件,否则这些目录和文件将以递归方式添加。
awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' selected-images-to-copy.txt > selected-images-to-copy-with-explicit-arborescences.txt
tar cf - --no-recursion -T selected-images-to-copy-with-explicit-arborescences.txt | pigz | pv | nc 1.1.1.1 2222
如果你确实想即时执行此操作,请使用 bash 的<()
构造:
tar cf - --no-recursion -T <(awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' selected-images-to-copy.txt) | pigz | pv | nc 1.1.1.1 2222
awk 命令只是重建并添加路径,每次添加一个目录级别,直到文件本身。
这样,要保存的文件路径中的任何目录也会被放入存档中,但不会--no-recursion
发生其他任何事情。因此,文件之前的每个目录所有权都将被正确保存和恢复。
还有一个性能问题需要在某些地方进行权衡:会有许多重复的树状结构,因此第二个 tar 通常会在同一个基本目录上重新执行 chown。您可以对 awk 的结果进行 sort -u 以删除所有重复项,但 sort 可能需要很长时间才能给出结果并开始传输。使用一个简短的 perl 脚本将唯一元素存储在内存中(权衡是内存使用,但我怀疑这不是一个问题),无需进行排序即可毫不延迟地输出唯一条目。因此解决方案变为:
tar cf - --no-recursion -T <(awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' selected-images-to-copy.txt | perl -w -e 'use strict; my %unique; while (<>) { if (not $unique{$_}++) { print } }' ) | pigz | pv | nc 1.1.1.1 2222
编辑:如果的内容selected-images-to-copy.txt
或多或少是一个经过排序的文件列表(find
[...]-type f
类型的命令的未排序输出已经足够好了),这里有一个不需要任何内存使用的解决方案(这确实可能成为数亿个条目的问题)只需记住最后一条最长的路径并将其与下一条路径进行比较就足够了:
- 下一个不是前一个的前缀,这意味着它是一个新的树状图(或同一树状图中的新文件)并且必须存档,在这种情况下设计新的“最后最长的路径”。 如果初始列表至少没有以树的形式呈现(至少在find
命令输出中,或者当然是排序列表),则会出现一些开始重复。
- 它是一个前缀(从第一个字符匹配的子字符串),这意味着它是一个已经看到的目录,因为它是前一个路径的一部分,可以安全地忽略它。
我/
在比较中添加了一个尾随,以便轻松找到mnt/a/b/foo/
不是 前缀的mnt/a/b/foobar
。使用mnt/a/b/foobar/file4.png
和mnt/a/b/foo/file5.png
作为输入,如果没有这个技巧,目录的所有权mnt/a/b/foo
就不会恢复。因此,perl 命令被替换为:
awk '{ if (index(old,$0 "/") != 1) { old=$0; print } }'
此示例:
file1.png
mnt/a/b/file2.png
mnt/a/b/file3.png
mnt/a/b/c/foobar/file4.png
mnt/a/b/c/foo/file5.png
mnt/a/b/file6.png
mnt/a/b/d/file7.png
通过这个过滤器:
awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' | awk '{ if (index(old,$0 "/") != 1) { old=$0; print } }'
使这些目录和文件准备好tar --no-recursion
:
file1.png
mnt
mnt/a
mnt/a/b
mnt/a/b/file2.png
mnt/a/b/file3.png
mnt/a/b/c
mnt/a/b/c/foobar
mnt/a/b/c/foobar/file4.png
mnt/a/b/c/foo
mnt/a/b/c/foo/file5.png
mnt/a/b/file6.png
mnt/a/b/d
mnt/a/b/d/file7.png
因此,使用整对命令的解决方案变成了(root 已经使用-p
和,并且当 a可以工作并且可以轻松地用 打破长行以提高可读性--same-owner
时最好放弃 bash 的花哨):<()
|
\
# TARGET (extract):
$ nc -l -p 2222 | pigz -d | sudo tar xf - -C /
# SOURCE:
$ awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' selected-images-to-copy.txt | \
awk '{ if (index(old,$0 "/") != 1) { old=$0; print } }' | \
tar cf - --no-recursion -T - | pigz | pv | nc -w 60 1.1.1.1 2222
答案2
- 创建时使用 -p 来保留权限(tar -cpvf file.tar bla bla)
- 提取时将 --same-owner 选项传递给 tar。(tar -xvf --same-owner file.tar)