Como eu poderia classificar os arquivos aos diretórios com base em nomes de arquivos?

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

  •  23-08-2019
  •  | 
  •  

Pergunta

Eu tenho um grande número de arquivos para classificar todos nomeados em alguma terrível convenção.
Aqui estão alguns exemplos:

(4) _mr__mcloughlin ____. Txt
12__sir_john_farr ____. Txt
(B) mr__chope ____. Txt
dame_elaine_kellett-Bowman ____. txt
dr__blackburn ______. txt

Estes nomes são suposto ser uma pessoa diferente (speaker) cada. Alguém em outro departamento de TI produziu estes de uma tonelada de arquivos XML usando algum script, mas a nomenclatura é incomensuravelmente estúpido como você pode ver.

Eu preciso espécie literalmente dezenas de milhares desses arquivos com vários arquivos de texto para cada pessoa; cada um com algo estúpido fazer o diferente nome de arquivo, seja ele mais sublinhados ou algum número aleatório. Eles precisam ser classificados por alto-falante.

Este seria mais fácil com um script para fazer a maior parte do trabalho, então eu só poderia voltar e pastas de mesclagem que devem estar sob o mesmo nome ou o que quer.

Há uma série de maneiras que eu estava pensando em fazer isso.

  • analisar os nomes de cada arquivo e classificá-los em pastas para cada nome exclusivo.
  • obter uma lista de todos os nomes originais dos nomes de arquivos, então olhar através desta lista simplificada de nomes exclusivos para os similares e perguntar-me se eles são os mesmos, e uma vez que tenha determinado isso, ele vai classificá-los todos em conformidade.

Estou pensando em usar Perl, mas posso tentar um novo idioma, se vale a pena. Não estou certo como ir sobre a leitura em cada arquivo em um diretório de cada vez em uma string para analisar em um nome real. Eu não estou completamente certo como analisar com regex em Perl, quer, mas que pode ser googleable.

Para a classificação, eu só ia usar o comando shell:

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

mas apenas porque isso é tudo que eu sei, por isso é mais fácil.

Eu não têm sequer uma idéia pseudocódigo do que Im que vão fazer ou então se alguém sabe a melhor sequência de ações, im todas as orelhas. Eu acho que eu estou olhando para um monte de ajuda, eu estou aberto a todas as sugestões. Muitos muitos muitos agradecimentos a quem puder ajudar.

B.

Foi útil?

Solução

Espero que eu entendi sua pergunta para a direita, é um IMHO ambígua bit. Este código não foi testado, mas deve fazer o que eu acho que você quer.

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");
    }
}

Outras dicas

São todos os arquivos atuais no mesmo diretório? Se for esse o caso, então você pode usar 'opendir' e 'readdir' para ler todos os arquivos um por um. Construir um hash usando o nome do arquivo como a chave (remover todos '_', bem como qualquer informação dentro dos colchetes) para que você obter algo como isto -

(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'

Definir o valor de hash para ser o número de instâncias do nome ocorreu até agora. Assim, após essas entradas você deve ter um hash parecida com esta -

'mr mcloughlin' => 1
'sir john farr' => 1
'mr chope' => 1
'dame elaine kellett-bowman' => 1
'dr blackburn' => 1

Sempre que você se deparar com uma nova entrada em sua mistura simplesmente criar um novo diretório usando o nome da chave. Agora tudo que você tem a fazer é copiar o arquivo com o nome alterado (use o valor hash correspondente como um sufixo) para o novo diretório. Assim, por exemplo., De você fosse para tropeçar em cima de uma outra entrada que se lê como 'McLoughlin mr' então você pode copiá-lo como

./mr mcloughlin/mr mcloughlin_2.txt

Eu faria:

  1. definir o que é significativo no nome:

    • é diferente dr__blackburn que dr_blackburn?
    • é diferente dr__blackburn que mr__blackburn?
    • são principais números significativa?
    • estão liderando / arrastando sublinhados significativa?
    • etc.
  2. chegar com regras e um algoritmo para converter um nome para um diretório (Leon é um começo muito bom)

  3. ler os nomes e processá-los um de cada vez

    • eu iria usar uma combinação de opendir e recursão
    • eu copiá-los como você processá-los; novo post de Leon é um grande exemplo
  4. Se este script terá de ser mantido e utilizado no futuro, eu gostaria defintely criar testes (por exemplo, usando http://search.cpan.org/dist/Test-More/ ) para cada caminho de expressão regular; quando você encontrar uma nova ruga, adicionar um novo teste e verifique se ele falhar, em seguida, corrigir o regex, então re-executar o teste para garantir que nada quebrou

Eu não usei Perl em quando então eu vou escrever isso em Ruby. Vou comentar que estabelecer algum pseudocódigo.

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   

Essa é a idéia, de qualquer maneira - eu tenho a certeza todas as chamadas de API estão corretas, mas isso não é testado código. Isto se parece com o que você está tentando realizar? Isso pode ajudá-lo a escrever o código em Perl?

Você pode dividir os nomes de arquivos usando algo como

@tokens = split /_+/, $filename

A última entrada de @tokens deve ser ".txt" para todos esses nomes, mas o segundo ao último deve ser semelhante para a mesma pessoa cujo nome foi grafada em locais (ou "Dr. Jones" alterado para "Brian Jones " por exemplo). Você pode querer usar algum tipo de editar distância como uma métrica de similaridade para comparar @tokens[-2] para vários nomes de arquivos ; quando duas entradas têm sobrenomes bastante semelhantes, eles devem pedir-lhe como um candidato para a fusão.

Como você está pedindo um muito geral questão, qualquer linguagem poderia fazer isso, desde que temos uma melhor codificação das regras. Nós nem sequer têm o especificidades , apenas uma "amostra".

Assim, cego de trabalho, parece que o monitoramento humano será necessário. Assim, a idéia é a peneira . Algo que você pode executar repetidamente e verifique e execute novamente e verifique novamente e novamente até que você tenha tudo resolvido para algumas pequenas tarefas manuais.

O código abaixo marcas um monte de suposições , porque você praticamente deixou para nós para lidar com isso. Uma delas é que a amostra é uma lista de todas as possíveis sobrenomes; se existem outros sobrenomes, adicione 'em e executá-lo novamente.

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 
                             )
        );
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top