我有一个大目录,其中包含我希望递归复制的子目录和文件。
有什么方法可以告诉cp
它应该按文件大小的顺序执行复制操作,以便首先复制最小的文件?
答案1
这是一种快速但肮脏的方法,使用rsync
.对于这个例子,我认为 10 MB 以下的任何内容都是“小”的。
首先只传输小文件:
rsync -a --max-size=10m srcdir dstdir
然后传输剩余的文件。之前传输的小文件不会被重新复制,除非被修改。
rsync -a srcdir dstdir
从man 1 rsync
--max-size=SIZE
This tells rsync to avoid transferring any file that is larger
than the specified SIZE. The SIZE value can be suffixed with a
string to indicate a size multiplier, and may be a fractional
value (e.g. "--max-size=1.5m").
This option is a transfer rule, not an exclude, so it doesn’t
affect the data that goes into the file-lists, and thus it
doesn’t affect deletions. It just limits the files that the
receiver requests to be transferred.
The suffixes are as follows: "K" (or "KiB") is a kibibyte
(1024), "M" (or "MiB") is a mebibyte (1024*1024), and "G" (or
"GiB") is a gibibyte (1024*1024*1024). If you want the multi‐
plier to be 1000 instead of 1024, use "KB", "MB", or "GB".
(Note: lower-case is also accepted for all values.) Finally, if
the suffix ends in either "+1" or "-1", the value will be offset
by one byte in the indicated direction.
Examples: --max-size=1.5mb-1 is 1499999 bytes, and
--max-size=2g+1 is 2147483649 bytes.
当然,逐个文件传输的顺序并不严格是从小到大,但我认为这可能是满足您的要求精神的最简单的解决方案。
答案2
这一次完成了整个工作 - 在所有子目录中,全部在单个流中,没有任何文件名问题。它将从小到大复制您拥有的每个文件。mkdir ${DESTINATION}
如果它尚不存在,您将需要这样做。
find . ! -type d -print0 |
du -b0 --files0-from=/dev/stdin |
sort -zk1,1n |
sed -zn 's/^[^0-9]*[0-9]*[^.]*//p' |
tar --hard-dereference --null -T /dev/stdin -cf - |
tar -C"${DESTINATION}" --same-order -xvf -
但你知道吗?这不做的是空的子目录。我可以对该管道进行一些重定向,但这只是等待发生的竞争条件。最简单的可能是最好的。所以之后就这样做:
find . -type d -printf 'mkdir -p "'"${DESTINATION}"'/%p"\n' |
. /dev/stdin
或者,由于吉尔斯在他的回答中提出了一个很好的观点,即保留目录权限,我也应该尝试一下。我认为这会做到这一点:
find . -type d -printf '[ -d "'"${DESTINATION}"'/%p" ] ||
cp "%p" -t "'"${DESTINATION}"'"\n' |
. /dev/stdin
我愿意打赌这比mkdir
无论如何都要快。
答案3
不是cp
直接的,这远远超出了它的能力范围。但您可以安排cp
以正确的顺序调用这些文件。
Zsh 方便地允许按大小对文件进行排序全局限定符。这是一个 zsh 片段,它按照大小递增的顺序从 under/path/to/source-directory
到 under复制文件/path/to/destination-directory
。
cd /path/to/source-directory
for x in **/*(.oL); do
mkdir -p /path/to/destination-directory/$x:h &&
cp -- $x /path/to/destination-directory/$x:h
done
您可以使用zcp
功能。但是,您需要首先创建目标目录,这可以在神秘的 oneliner 中完成。
autoload -U zmv; alias zcp='zmv -C'
cd /path/to/source-directory
mkdir -p **/*(/e\''REPLY=/path/to/destination-directory/$REPLY'\')
zcp -Q '**/*(.oL)' '/path/to/destination-directory/$f'
这不会保留源目录的所有权。如果您想要这样做,您需要使用合适的复制程序,例如cpio
或pax
。如果您这样做,则无需另外调用cp
或。zcp
cd /path/to/source-directory
print -rN -- **/*(^.) **/*(.oL) | cpio -0 -p /path/to/destination-directory
答案4
我认为没有任何方法可以cp -r
直接做到这一点。由于在获得神奇find
/解决方案之前可能需要一段不确定的时间awk
,因此这里有一个快速的 Perl 脚本:
#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);
use File::Find;
use File::Basename;
die "No (valid) source directory path given.\n"
if (!$ARGV[0] || !-d -r "/$ARGV[0]");
die "No (valid) destination directory path given.\n"
if (!$ARGV[1] || !-d -w "/$ARGV[1]");
my $len = length($ARGV[0]);
my @files;
find (
sub {
my $fpath = $File::Find::name;
return if !-r -f $fpath;
push @files, [
substr($fpath, $len),
(stat($fpath))[7],
]
}, $ARGV[0]
);
foreach (sort { $a->[1] <=> $b->[1] } @files) {
if ($ARGV[2]) {
print "$_->[1] $ARGV[0]/$_->[0] -> $ARGV[1]/$_->[0]\n";
} else {
my $dest = "$ARGV[1]/$_->[0]";
my $dir = dirname($dest);
mkdir $dir if !-e $dir;
`cp -a "$ARGV[0]/$_->[0]" $dest`;
}
}
用这个:
./whatever.pl /src/path /dest/path
参数应该都是绝对路径;
~
,或者 shell 扩展为绝对路径的任何其他内容都可以。如果您添加第三个参数(任何东西,除了文字
0
),它不会复制它,而是打印到标准输出它将做什么的报告,并以字节为单位预先考虑文件大小,例如4523 /src/path/file.x -> /dest/path/file.x 12124 /src/path/file.z -> /dest/path/file.z
请注意,这些是按大小升序排列的。
第 34 行的命令
cp
是一个字面 shell 命令,因此您可以使用开关执行任何您想要的操作(我只是用来-a
保留所有特征)。File::Find
和File::Basename
都是核心模块,即它们在所有 perl 安装中都可用。