0

我有大量的文件要按照一些糟糕的约定对所有文件进行排序。
这里有些例子:

(4)_mr__mcloughlin____.txt
12__sir_john_farr____.txt
(b)mr__chope____.txt
dame_elaine_kellett-bowman____.txt
dr__blackburn__.txt

这些名字应该是不同的人(说话者)。另一个 IT 部门的某个人使用一些脚本从大量 XML 文件中生成了这些文件,但正如您所见,命名非常愚蠢。

我需要为每个人使用多个文本文件对数以万计的这些文件进行排序;每个都有一些愚蠢的东西使文件名不同,无论是更多的下划线还是一些随机数。它们需要按扬声器分类。

使用脚本来完成大部分工作会更容易,然后我可以返回并合并应该使用相同名称或其他名称的文件夹。

我有很多方法可以考虑这样做。

  • 解析每个文件中的名称并将它们分类到每个唯一名称的文件夹中。
  • 从文件名中获取所有唯一名称的列表,然后查看此简化的唯一名称列表中的相似名称,并询问它们是否相同,一旦确定这一点,它将相应地对它们进行排序。

我打算使用 Perl,但如果值得的话,我可以尝试一门新语言。我不确定如何将目录中的每个文件名一次读入一个字符串以解析为实际名称。我也不完全确定如何在 perl 中使用正则表达式进行解析,但这可能是可搜索的。

对于排序,我只是要使用 shell 命令:

`cp filename.txt /example/destination/filename.txt`

但只是因为这就是我所知道的,所以这是最简单的。

我什至不知道我要做什么的伪代码想法,所以如果有人知道最佳的动作顺序,我会全神贯注。我想我正在寻找很多帮助,我愿意接受任何建议。非常感谢任何可以提供帮助的人。

B.

4

6 回答 6

5

我希望我能正确理解您的问题,恕我直言,这有点模棱两可。这段代码未经测试,但应该做我认为你想要的。

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");
    }
}
于 2009-02-16T09:05:44.677 回答
2

我有一段时间没有使用 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 中编写代码吗?

于 2009-02-16T07:58:22.420 回答
2

所有当前文件都在同一个目录中吗?如果是这种情况,那么您可以使用 '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
于 2009-02-16T08:15:25.593 回答
2

我会:

  1. 定义名称中的重要内容:

    • dr__blackburn不同于dr_blackburn? _
    • dr__blackburn不同于mr__blackburn? _
    • 前导数字有意义吗?
    • 前导/尾随下划线有意义吗?
    • 等等
  2. 提出将名称转换为目录的规则和算法(Leon's 是一个很好的开始)

  3. 读入名称并一次处理一个

    • 我会使用 opendir 和递归的某种组合
    • 我会在您处理它们时复制它们;Leon 的帖子又是一个很好的例子
  4. 如果将来需要维护和使用此脚本,我肯定会为每个正则表达式路径创建测试(例如使用http://search.cpan.org/dist/Test-More/);当你发现新的皱纹时,添加一个新的测试并确保它失败,然后修复正则表达式,然后重新运行测试以确保没有损坏

于 2009-02-16T18:54:48.440 回答
1

您可以使用类似的东西拆分文件名

@tokens = split /_+/, $filename

的最后一个条目@tokens应该是".txt"所有这些文件名,但倒数第二个条目应该是相似的,因为同一个人的名字在某些地方拼写错误(或者“Dr. Jones”更改为“Brian Jones”)。您可能希望使用某种编辑距离作为相似性度量来比较@tokens[-2]各种文件名;当两个条目的姓氏足够相似时,它们应该提示您作为合并的候选者。

于 2009-02-16T08:12:13.820 回答
1

正如您提出的一个非常笼统的问题,只要我们有更好的规则编纂,任何语言都可以做到这一点。我们甚至没有细节,只有一个“样本”。

所以,盲目地工作,看起来需要人工监控。所以这个想法是一个筛子。您可以反复运行和检查并再次运行并反复检查,直到您将所有内容分类为一些小的手动任务。

下面的代码做了很多假设,因为你几乎把它留给了我们来处理。其中之一是样本是所有可能的姓氏的列表;如果还有其他姓氏,请添加它们并再次运行它。

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 
                             )
        );
}
于 2009-02-16T21:44:52.153 回答