English 中文(简体)
我应该如何根据文件名将文件分类到文件夹中?
原标题:
  • 时间:2009-02-16 07:40:44
  •  标签:

I have a huge number of files to sort all named in some terrible convention.
Here are some examples:

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

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

我需要分类成千上万个这些文件,每个人都有多个文本文件,每个文件都有一些愚蠢的东西使文件名不同,可能是更多的下划线或一些随机数字。它们需要按发言人进行排序。

这将会更容易,如果有一个脚本去处理大部分工作,这样我就可以回去合并那些应该被放在相同名称下的文件夹。

我想到了几种方法来做这件事。

  • parse the names from each file and sort them into folders for each unique name.
  • get a list of all the unique names from the filenames, then look through this simplified list of unique names for similar ones and ask me whether they are the same, and once it has determined this it will sort them all accordingly.

我计划使用Perl,但如果值得的话,我可以尝试一种新的语言。我不确定如何逐个读取目录中的每个文件名,并将其转换为字符串以进行实际名称的解析。我也不完全确定如何在Perl中使用正则表达式解析,但这可能可以通过谷歌解决。

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

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

但只是因为那是我所知道的,所以它最容易。

我甚至没有任何伪代码的想法,所以如果有人知道最好的操作序列,我全听着呢。我想我需要很多帮助,我对任何建议都很开放。非常非常感谢任何能够帮忙的人。

Sorry, this statement is incomplete. Please provide more context or information for me to translate.

最佳回答

我希望我正确理解了你的问题,但我认为它有些含糊不清。这段代码未经测试,但应该能够实现我认为你想要的功能。

use File::Copy;

sub sanatize {
    local $_ = shift;
    s/(?:dame|dr|mr|sir)|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

每当您遇到哈希表中的新条目时,只需使用键名创建一个新目录。现在您只需要将更改名称的文件副本(使用相应的哈希值作为后缀)复制到新目录中即可。例如,如果您遇到另一个条目,读作“mrmcloughlin”,则可以将其复制为

./mr mcloughlin/mr mcloughlin_2.txt

我会:

  1. 定义名称中的重要之处:

    • is dr__blackburn different than dr_blackburn?
    • is dr__blackburn different than mr__blackburn?
    • are leading numbers meaningful?
    • are leading/trailing underscores meaningful?
    • etc.
  2. 想出规则和算法,将姓名转换成目录(Leon的姓名是很好的起点)

  3. 逐一读取名称并逐一处理。

    • I would use some combination of opendir and recursion
    • I would copy them as you process them; again Leon s post is a great example
  4. 如果这个脚本需要在将来进行维护和使用,我一定会为每个正则表达式路径创建测试(例如使用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. Jones”更改为“Brian Jones”)的倒数第二个应该相似。您可能希望使用某种编辑距离度量来比较各种文件名的@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 
                             )
        );
}




相关问题
热门标签