Как мне сортировать файлы по каталогам по именам файлов?

StackOverflow https://stackoverflow.com/questions/552512

  •  23-08-2019
  •  | 
  •  

Вопрос

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

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

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

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

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

Я думал о нескольких способах сделать это.

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

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

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

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

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

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

Б.

Это было полезно?

Решение

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

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_2.txt

Я бы:

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

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

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

    • Я бы использовал комбинацию opendir и рекурсии
    • Я бы копировал их по мере того, как вы их обрабатываете;И снова пост Леона - отличный пример
  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.Джонс» изменилось на «Брайан Джонс», например).Возможно, вы захотите использовать какой-то изменить расстояние как показатель сходства для сравнения @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 
                             )
        );
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top