需要找到要复制的文件,因为它们没有给出路径(不知道它们在哪个目录中),但重新搜索每个文件非常浪费,大大增加了复杂性。
相反,首先为每个文件名构建一个具有完整路径名的哈希。
一种方法是使用 Perl,利用快速核心模块文件::查找 https://perldoc.perl.org/File/Find.html
use warnings;
use strict;
use feature 'say';
use File::Find;
use File::Copy qw(copy);
my $source_dir = shift // '/path/to/source'; # give at invocation or default
my $copy_to_dir = '/path/to/destination';
my $file_list = 'file_list_to_copy.txt';
open my $fh, '<', $file_list or die "Can't open $file_list: $!";
my @files = <$fh>;
chomp @files;
my %fqn;
find( sub { $fqn{$_} = $File::Find::name unless -d }, $source_dir );
# Now copy the ones from the list to the given location
foreach my $fname (@files) {
copy $fqn{$fname}, $copy_to_dir
or do {
warn "Can't copy $fqn{$fname} to $copy_to_dir: $!";
next;
};
}
The remaining problem is about filenames that may exists in multiple directories, but we need to be given a rule for what to do then.†
我忽略了问题中使用的最大深度,因为它是无法解释的,并且在我看来是与极端运行时间相关的修复(?)。此外,根据问题的提示,文件被复制到“平面”结构中(不恢复其原始层次结构)。
最后,我只跳过目录,而其他各种文件类型都有自己的问题(复制周围的链接需要小心)。仅接受纯文件更改unless -d
to if -f
.
† A clarification came that, indeed, there may be files with the same name in different directories. Those should be copied to same name suffixed with a sequential number before the extension.
For this we need to check whether a name exists already, and to keep track of duplicate ones, while building the hash, so this will take a little longer. There is a little conundrum of how to account for duplicate names then? I use another hash where only duped-names‡ are kept, in arrayrefs; this simplifies and speeds up both parts of the job.
my (%fqn, %dupe_names);
find( sub {
return if -d;
(exists $fqn{$_})
? push( @{ $dupe_names{$_} }, $File::Find::name )
: ( $fqn{$_} = $File::Find::name );
}, $source_dir );
令我惊讶的是,在散布在庞大的层次结构中的 25 万个文件上,即使现在对每个项目运行测试,它的运行速度也只比代码慢一点,而不用担心重复的名称。
中赋值周围的括号三元运算符 https://perldoc.perl.org/perlop.html#Conditional-Operator需要,因为操作符可能会被赋值(如果最后两个参数是有效的“左值”,就像它们在这里一样),所以需要小心分支内的赋值。
然后复制后%fqn
与帖子的主要部分一样,也复制其他同名文件。我们需要分解文件名以便在之前添加枚举.ext
;我用核心文件::基本名称 https://perldoc.perl.org/File/Basename.html
use File::Basename qw(fileparse);
foreach my $fname (@files) {
next if not exists $dupe_names{$fname}; # no dupe (and copied already)
my $cnt = 1;
foreach my $fqn (@{$dupe_names{$fname}}) {
my ($name, $path, $ext) = fileparse($fqn, qr/\.[^.]*/);
copy $fqn, "$copy_to_dir/${name}_$cnt$ext";
or do {
warn "Can't copy $fqn to $copy_to_dir: $!";
next;
};
++$cnt;
}
}
(已完成基本测试,但仅此而已)
我也许会用undef
代替$path
上面,表明该路径未使用(同时这也避免了分配和填充标量),但为了让那些不熟悉模块的子返回内容的人清楚起见,我将其保留为这种方式。
Note.对于有重复的文件,会有副本fname.ext
, fname_1.ext
等等。如果您愿意的话all索引,然后首先重命名fname.ext
(在目的地,它已经通过复制%fqn
) to fname_1.ext
,并将计数器初始化更改为my $cnt = 2;
.
‡ These by no means need be same files.