Как бы отсортировать файлы в каталогах на основе имен файлов? - PullRequest
0 голосов
/ 16 февраля 2009

У меня есть огромное количество файлов для сортировки всех названных по какому-то ужасному соглашению.
Вот несколько примеров:

(4) _mr__mcloughlin ____. TXT
12__sir_john_farr ____. TXT
(Б) mr__chope ____. TXT
dame_elaine_kellett-лучник ____. TXT
dr__blackburn ______. txt

Эти имена должны быть разными (носителями). Кто-то в другом ИТ-отделе произвел их из тонны XML-файлов, используя какой-то сценарий, но, как вы можете видеть, наименование глупо глупо.

Мне нужно отсортировать буквально десятки тысяч этих файлов с несколькими текстовыми файлами для каждого человека; каждый с чем-то глупым, делающим имя файла другим, будь то знак подчеркивания или случайное число. Их нужно отсортировать по динамику.

Это было бы проще, если бы скрипт выполнял большую часть работы, тогда я мог бы просто вернуться назад и объединить папки, которые должны быть под тем же именем или чем-то еще.

Есть несколько способов, которыми я думал об этом.

  • разбирает имена из каждого файла и сортирует их по папкам для каждого уникального имени.
  • получить список всех уникальных имен из имен файлов, затем просмотреть этот упрощенный список уникальных имен для похожих и спросить меня, совпадают ли они, и как только он определит это, он отсортирует их все соответствующим образом.

Я планирую использовать Perl, но я могу попробовать новый язык, если оно того стоит. Я не уверен, как перейти к чтению каждого имени файла в каталоге по очереди в строку для анализа действительного имени. Я не совсем уверен, как разобрать с regex в Perl, но это может быть googleable.

Для сортировки я просто собирался использовать команду оболочки:

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

но только потому, что это все, что я знаю, так что проще всего.

У меня даже нет идеи псевдокода о том, что я собираюсь делать, поэтому, если кто-то знает лучшую последовательность действий, я весь слух. Я думаю, я ищу много помощи, я открыт для любых предложений. Большое, большое спасибо всем, кто может помочь.

B.

Ответы [ 6 ]

5 голосов
/ 16 февраля 2009

Надеюсь, я правильно понял ваш вопрос, это немного двусмысленно ИМХО. Этот код не проверен, но должен делать то, что я думаю, что вы хотите.

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");
    }
}
2 голосов
/ 16 февраля 2009

Я бы:

  1. определить, что значимо в названии:

    • dr__blackburn отличается от dr_blackburn?
    • отличается dr__blackburn от mr__blackburn?
    • значат ли ведущие числа?
    • значат начальные / конечные подчеркивания?
    • и т.д.
  2. придумать правила и алгоритм для преобразования имени в каталог (Леон - очень хорошее начало)

  3. читать имена и обрабатывать их по одному

    • Я бы использовал некоторую комбинацию opendir и рекурсии
    • Я бы скопировал их по мере их обработки; опять же, пост Леона - отличный пример
  4. если этот скрипт нужно будет поддерживать и использовать в будущем, я бы определенно создал тесты (например, используя http://search.cpan.org/dist/Test-More/) для каждого пути регулярного выражения; когда вы найдете новую складку, добавьте новый тест и убедитесь, что это не удалось, затем исправьте регулярное выражение, затем повторите тест, чтобы убедиться, что ничего не сломалось

2 голосов
/ 16 февраля 2009

Все ли текущие файлы находятся в одном каталоге? Если это так, то вы можете использовать «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
2 голосов
/ 16 февраля 2009

Я давно не пользовался 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?

1 голос
/ 17 февраля 2009

Поскольку вы задаете очень общий вопрос, любой язык может сделать это, если у нас есть лучшая кодификация правил. У нас даже нет специфика , только "образец".

Итак, работая вслепую, похоже, что человеческий мониторинг будет необходим. Так что идея состоит в сите . Что-то, что вы можете многократно запускать, проверять и запускать снова и снова, проверять снова и снова, пока вы не разберетесь с несколькими небольшими ручными задачами.

Приведенный ниже код делает множеством предположений , потому что вы в значительной степени предоставили нам справиться с этим. Одним из них является то, что образец представляет собой список всех возможных фамилий; если есть другие фамилии, добавьте их и запустите снова.

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 
                             )
        );
}
1 голос
/ 16 февраля 2009

Вы можете разделить имена файлов, используя что-то вроде

@tokens = split /_+/, $filename

Последняя запись @tokens должна быть ".txt" для всех этих имен файлов, но второе-последнее должно быть одинаковым для того же человека, чье имя было написано с ошибками в некоторых местах (или изменился «Доктор Джонс») к "Брайану Джонсу", например). Вы можете использовать какое-то расстояние редактирования в качестве показателя сходства для сравнения @tokens[-2] для различных имен файлов; если две записи имеют одинаковые фамилии, они должны предложить вам кандидатуру для слияния.

...