¿Cómo iba a ordenar los archivos a los directorios basados ​​en nombres de archivo?

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

  •  23-08-2019
  •  | 
  •  

Pregunta

Tengo una enorme cantidad de archivos para ordenar todos los nombrados en algún terrible convención.
He aquí algunos ejemplos:

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

Estos nombres se supone que es una persona diferente (altavoz) cada uno. Alguien en otro departamento de TI produce estos de una tonelada de archivos XML utilizando una secuencia de comandos, pero la nomenclatura es insondablemente estúpida como se puede ver.

Necesito clasificar literalmente decenas de miles de estos archivos con varios archivos de texto para cada persona; cada uno con algo estúpido haciendo que el nombre de archivo diferente, ya sea más guiones o algún número aleatorio. Tienen que ser ordenado por el altavoz.

Esto sería más fácil con una secuencia de comandos para hacer la mayoría del trabajo, entonces tan sólo pudiera volver atrás y fusionar las carpetas que deben estar bajo el mismo nombre o lo que sea.

Hay un número de maneras que estaba pensando en hacer esto.

  • analizar los nombres de todos los archivos y ordenarlos en carpetas para cada nombre único.
  • obtener una lista de todos los nombres únicos de los nombres de archivo, y luego mirar a través de esta lista simplificada de nombres únicos para las similares y me pregunta si son lo mismo, y una vez que se ha determinado que este se va a clasificar a todos ellos en consecuencia.

Pienso en el uso de Perl, pero puedo probar un nuevo idioma si vale la pena. No estoy seguro de cómo ir sobre la lectura en cada nombre de archivo en un directorio de una en una en una cadena para analizar en un nombre real. No estoy completamente seguro de cómo analizar con expresiones regulares en Perl o bien, pero que podría ser Googleable.

Para la clasificación, sólo estaba a usar el comando shell:

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

pero sólo porque eso es todo lo que sé lo que es más fácil.

Yo ni siquiera tienen una idea de lo que pseudocódigo im va a hacer, ya sea por lo que si alguien sabe la mejor secuencia de acciones, im todos los oídos. Creo que estoy en busca de mucha ayuda, estoy abierto a cualquier sugerencia. Muchas muchas muchas gracias a cualquiera que pueda ayudar.

B.

¿Fue útil?

Solución

Espero entiendo su pregunta correcta, que es un poco ambiguas, en mi humilde opinión. Este código no se ha probado, pero debe hacer lo que creo que quiere.

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

Otros consejos

¿Son todos los archivos actuales en el mismo directorio? Si ese es el caso, entonces usted podría utilizar 'opendir' y 'readdir' para leer a través de todos los archivos de uno en uno. Construir un hash utilizando el nombre de archivo que la llave (eliminar todos '_', así como cualquier información dentro de los corchetes) de manera que se obtiene algo como esto -

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

Establecer el valor del hash para ser el número de instancias del nombre ocurrido hasta ahora. Así que después de estas entradas debe tener un hash que tiene este aspecto -

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

Cada vez que se encuentra con una nueva entrada en su hash de simplemente crear un nuevo directorio utilizando el nombre de clave. Ahora todo lo que tiene que hacer es copiar el archivo con el nombre cambiado (utilizar el valor hash correspondiente como sufijo) en el nuevo directorio. Así, por ejemplo., De que iban a tropezar con otra entrada cuyo texto es el 'Sr. McLoughlin' entonces se podría copiar como

./mr mcloughlin/mr mcloughlin_2.txt

lo haría:

  1. definir lo que es significativo en el nombre:

    • se dr__blackburn diferente que dr_blackburn?
    • se dr__blackburn diferente que mr__blackburn?
    • los números son los principales significativa?
    • encabezan / arrastre de relieve significativo?
    • etc.
  2. vienen con reglas y un algoritmo para convertir un nombre en un directorio (Leon es un muy buen comienzo)

  3. leer los nombres y procesarlos uno a la vez

    • I utilizaría una combinación de opendir y recursividad
    • Me copiarlos a medida que los procesos; de nuevo el post de León es un gran ejemplo
  4. si tendrá que ser mantenido y utilizado en el futuro este guión, me gustaría crear definitivamente las pruebas (por ejemplo, usando http://search.cpan.org/dist/Test-More/ ) para cada trayecto de expresión regular; cuando encuentre un nuevo giro, añadir una nueva prueba y asegúrese de que falla, a continuación, fijar la expresión regular, a continuación, volver a ejecutar la prueba para asegurarse de que nada se rompió

No he utilizado Perl desde hace tiempo, así que voy a escribir esto en Rubí. Voy a comentar que establecer algún 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   

Esa es la idea, de todos modos - me he asegurado de todas las llamadas a la API son correctos, pero esto no se ha probado código. Cómo se ve esto lo que estamos tratando de lograr? Podría esto ayudará a escribir el código en Perl?

Puede dividir los nombres de archivo utilizando algo así como

@tokens = split /_+/, $filename

La última entrada de @tokens debe ".txt" para todos estos nombres de archivo, pero la segunda a la última debe ser similar para la misma persona cuyo nombre ha sido mal escrito en los lugares (o "Dr. Jones" ha cambiado a "Brian Jones " por ejemplo). Es posible que desee utilizar algún tipo de href="http://en.wikipedia.org/wiki/Edit_distance" distancia de edición como una métrica de similitud para comparar @tokens[-2] para diversos nombres de archivo ; cuando dos entradas tienen suficientes apellidos similares, deben pedirá como candidato para la fusión.

A medida que usted está pidiendo una pregunta muy general , cualquier lenguaje podría hacer esto todo el tiempo que tenemos una mejor codificación de las normas. Ni siquiera tenemos los Detalles , sólo una "muestra".

Así que, trabajando a ciegas, parece que será necesaria una vigilancia humana. Así que la idea es un tamiz . Algo que se puede ejecutar varias veces y compruebe y correr de nuevo y comprobar una y otra vez hasta que haya logrado todo resuelto a algunas pequeñas tareas manuales.

El siguiente código marcas una gran cantidad de supuestos , ya que prácticamente dejó a nosotros para manejarlo. Una de ellas es que la muestra es una lista de todos los posibles nombres últimos; si hay otros apellidos, añadir ellos y ejecutarlo de nuevo.

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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top