题
我有大量文件需要对所有以某种可怕的约定命名的文件进行排序。
这里有些例子:
(4)_mr__mcloughlin____.txt
12__sir_john_farr____.txt
(b)mr__chope____.txt
dame_elaine_kellett-bowman____.txt
布莱克本博士__.txt
这些名字应该是不同的人(说话者)。另一个 IT 部门的某人使用一些脚本从大量 XML 文件中生成了这些文件,但如您所见,命名极其愚蠢。
我需要对数以万计的文件进行排序,每个人都有多个文本文件;每个都有一些愚蠢的东西使文件名不同,无论是更多下划线还是一些随机数。它们需要按演讲者排序。
使用脚本来完成大部分工作会更容易,然后我可以返回并合并应该具有相同名称或其他名称的文件夹。
我考虑过很多方法来做到这一点。
- 解析每个文件中的名称,并将它们分类到每个唯一名称的文件夹中。
- 从文件名中获取所有唯一名称的列表,然后查看这个简化的唯一名称列表中是否有相似的名称,并询问我它们是否相同,一旦确定了这一点,它就会相应地对它们进行排序。
我计划使用 Perl,但如果值得的话我可以尝试一种新语言。我不知道如何将目录中的每个文件名一次一个地读入字符串中,以便解析为实际名称。我也不完全确定如何在 perl 中使用正则表达式进行解析,但这可能可以通过谷歌搜索。
对于排序,我只需要使用 shell 命令:
`cp filename.txt /example/destination/filename.txt`
但因为这就是我所知道的所以这是最简单的。
我什至不知道我要做什么,所以如果有人知道最佳的操作顺序,我会洗耳恭听。我想我正在寻求很多帮助,我愿意接受任何建议。非常非常感谢任何可以提供帮助的人。
B.
解决方案
我希望我正确理解你的问题,恕我直言,这有点含糊。这段代码未经测试,但应该做我认为你想要的。
use File::Copy;
sub sanatize {
local $_ = shift;
s/\b(?:dame|dr|mr|sir)\b|\d+|\(\w+\)|.txt$//g;
s/[ _]+/ /g;
s/^ | $//g;
return lc $_;
}
sub sort_files_to_dirs {
my @files = @_;
for my $filename (@files) {
my $dirname = sanatize($filename);
mkdir $dirname if not -e $dirname;
copy($filename, "$dirname/$filename");
}
}
其他提示
当前所有文件都在同一目录中吗?如果是这种情况,那么您可以使用“opendir”和“readdir”来一一读取所有文件。使用文件名作为键构建哈希(删除所有“_”以及括号内的任何信息),以便您得到类似的内容 -
(4)_mr__mcloughlin____.txt -> 'mr mcloughlin'
12__sir_john_farr____.txt -> 'sir john farr'
(b)mr__chope____.txt -> 'mr chope'
dame_elaine_kellett-bowman____.txt -> 'dame elaine kellett-bowman'
dr__blackburn______.txt -> 'dr blackburn'
将哈希值设置为迄今为止出现的名称实例的数量。因此,在这些条目之后,您应该有一个如下所示的哈希值 -
'mr mcloughlin' => 1
'sir john farr' => 1
'mr chope' => 1
'dame elaine kellett-bowman' => 1
'dr blackburn' => 1
每当您在哈希中遇到新条目时,只需使用键名称创建一个新目录即可。现在您所要做的就是将更改后的名称(使用相应的哈希值作为后缀)的文件复制到新目录中。例如,如果您偶然发现另一个条目,其内容为“mr mcloughlin”,那么您可以将其复制为
./mr mcloughlin/mr mcloughlin_2.txt
我会:
定义名称中的重要含义:
- 是
dr__blackburn
不同于dr_blackburn
? - 是
dr__blackburn
不同于mr__blackburn
? - 前导数字有意义吗?
- 前导/尾随下划线有意义吗?
- ETC。
- 是
提出将名称转换为目录的规则和算法(Leon 是一个非常好的开始)
读入名称并一次处理一个
- 我会使用 opendir 和递归的某种组合
- 当你处理它们时我会复制它们;莱昂的帖子再次是一个很好的例子
如果这个脚本将来需要维护和使用,我肯定会创建测试(例如使用 http://search.cpan.org/dist/Test-More/) 对于每个正则表达式路径;当您发现新的问题时,添加新的测试并确保它失败,然后修复正则表达式,然后重新运行测试以确保没有任何问题
我已经有一段时间没有使用 Perl 了,所以我打算用 Ruby 来写这个。我将对其进行评论以建立一些伪代码。
DESTINATION = '/some/faraway/place/must/exist/and/ideally/be/empty'
# get a list of all .txt files in current directory
Dir["*.txt"].each do |filename|
# strategy:
# - chop off the extension
# - switch to all lowercase
# - get rid of everything but spaces, dashes, letters, underscores
# - then swap any run of spaces, dashes, and underscores for a single space
# - then strip whitespace off front and back
name = File.basename(filename).downcase.
gsub(/[^a-z_\s-]+/, '').gsub(/[_\s-]+/, ' ').strip
target_folder = DESTINATION + '/' + name
# make sure we dont overwrite a file
if File.exists?(target_folder) && !File.directory?(target_folder)
raise "Destination folder is a file"
# if directory doesnt exist then create it
elsif !File.exists?(target_folder)
Dir.mkdir(target_folder)
end
# now copy the file
File.copy(filename, target_folder)
end
无论如何,这就是想法 - 我已经确保所有 API 调用都是正确的,但这不是经过测试的代码。这看起来像您想要实现的目标吗?这可以帮助您用 Perl 编写代码吗?
您可以使用类似的方法分割文件名
@tokens = split /_+/, $filename
最后一个条目为 @tokens
应该 ".txt"
对于所有这些文件名,但对于名字在某些地方拼写错误的同一个人来说,倒数第二个文件名应该相似(或“Dr.例如,“琼斯”更改为“布莱恩·琼斯”)。您可能想使用某种 编辑距离 作为比较的相似性度量 @tokens[-2]
对于各种文件名;当两个条目的姓氏足够相似时,它们应该提示您作为合并的候选者。
当你问一个 很一般 问题是,只要我们有更好的规则编纂,任何语言都可以做到这一点。我们甚至没有 具体细节, ,只是一个“样本”。
因此,盲目工作似乎需要人工监控。所以这个想法是 筛. 。您可以重复运行、检查、再次运行、一次又一次检查,直到将所有内容分类为一些小的手动任务。
下面的代码使 很多假设, ,因为你几乎把它留给了我们来处理。其中之一是样本是所有可能的姓氏的列表;如果还有其他姓氏,请添加它们并再次运行。
use strict;
use warnings;
use File::Copy;
use File::Find::Rule;
use File::Spec;
use Readonly;
Readonly my $SOURCE_ROOT => '/mess/they/left';
Readonly my $DEST_DIRECTORY => '/where/i/want/all/this';
my @lname_list = qw<mcloughlin farr chope kelette-bowman blackburn>;
my $lname_regex
= join( '|'
, sort { ( $b =~ /\P{Alpha}/ ) <=> ( $a =~ /\P{Alpha}/ )
|| ( length $b ) <=> ( length $a )
|| $a cmp $b
} @lname_list
)
;
my %dest_dir_for;
sub get_dest_directory {
my $case = shift;
my $dest_dir = $dest_dir_for{$case};
return $dest_dir if $dest_dir;
$dest_dir = $dest_dir_for{$case}
= File::Spec->catfile( $DEST_DIRECTORY, $case )
;
unless ( -e $dest_dir ) {
mkdir $dest_dir;
}
return $dest_dir;
}
foreach my $file_path (
File::Find::Rule->file
->name( '*.txt' )->in( $SOURCE_ROOT )
) {
my $file_name = [ File::Spec->splitpath( $file_path ) ]->[2];
$file_name =~ s/[^\p{Alpha}.-]+/_/g;
$file_name =~ s/^_//;
$file_name =~ s/_[.]/./;
my ( $case ) = $file_name =~ m/(^|_)($lname_regex)[._]/i;
next unless $case;
# as we next-ed, we're dealing with only the cases we want here.
move( $file_path
, File::Spec->catfile( get_dest_directory( lc $case )
, $file_name
)
);
}