Comment puis-je trier les fichiers dans des répertoires basés sur les noms de fichiers?

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

  •  23-08-2019
  •  | 
  •  

Question

J'ai un grand nombre de fichiers pour trier tous nommés dans une convention horrible.
Voici quelques exemples:

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

Ces noms sont censés être une autre personne (haut-parleur) chacun. Quelqu'un dans un autre service informatique ont produit ces d'une tonne de fichiers XML à l'aide un script, mais la dénomination est stupide insondables comme vous pouvez le voir.

Je dois trier littéralement des dizaines de milliers de ces fichiers avec plusieurs fichiers de texte pour chaque personne; chacun avec quelque chose de stupide rendant le nom de fichier différent, que ce soit plus ou underscores un nombre aléatoire. Ils doivent être triés par haut-parleur.

Ce serait plus facile avec un script pour faire la plupart des travaux alors que je pouvais revenir en arrière et fusionner des dossiers qui devraient être sous le même nom ou autre.

Il y a un certain nombre de façons je pensais à le faire.

  • parser les noms de chaque fichier et les classer dans des dossiers pour chaque nom unique.
  • obtenir une liste de tous les noms uniques des noms de fichiers, puis regardez à travers cette liste simplifiée des noms uniques pour ceux similaires et me demander si ce sont les mêmes, et une fois qu'il a déterminé cela, il les classera tout en conséquence.

Je prévois d'utiliser Perl, mais je peux essayer une nouvelle langue si ça vaut le coup. Je ne sais pas comment aller sur la lecture dans chaque nom de fichier dans un répertoire un à la fois dans une chaîne pour l'analyse syntaxique en un nom réel. Je ne suis pas tout à fait sûr de savoir comment analyser avec regex en Perl soit, mais cela pourrait être googleable.

Pour le tri, j'allais juste utiliser la commande shell:

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

mais juste parce que c'est tout ce que je sais il est donc plus facile.

Je DonT même pas une idée de ce pseudo-code im va faire soit si quelqu'un connaît la meilleure séquence d'actions, im toutes les oreilles. Je suppose que je suis à la recherche de beaucoup d'aide, je suis ouvert à toute suggestion. Beaucoup de merci beaucoup à tous ceux qui peuvent aider.

B.

Était-ce utile?

La solution

J'espère que je comprends bien votre question, il est un peu à mon humble avis ambigu. Ce code est non testé, mais devrait faire ce que je pense que vous voulez.

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

Autres conseils

sont tous les fichiers en cours dans le même répertoire? Si tel est le cas, vous pouvez alors utiliser « opendir » et « readdir » pour lire tous les fichiers un par un. Construire un hachage en utilisant le nom de fichier comme la clé (supprimer tous les « _ », ainsi que toute information à l'intérieur des crochets) afin que vous obtenez quelque chose comme ça -

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

Définir la valeur du hachage pour être le nombre d'instances du nom a eu lieu jusqu'à présent. Donc, après ces entrées, vous devriez avoir un hachage qui ressemble à ceci -

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

Chaque fois que vous tombez sur une nouvelle entrée dans votre hachage il suffit de créer un nouveau répertoire en utilisant le nom de clé. Maintenant, tout ce que vous avez à faire est de copier le fichier avec le nom modifié (utilisez la valeur de hachage correspondante comme suffixe) dans le nouveau répertoire. Ainsi, par exemple., De vous deviez tomber par hasard sur une autre entrée qui se lit comme M. McLoughlin 'vous pouvez copier ensuite comme

./mr mcloughlin/mr mcloughlin_2.txt

Je voudrais:

  1. définir ce qui est important dans le nom:

    • est dr__blackburn différent de dr_blackburn?
    • est dr__blackburn différent de mr__blackburn?
    • sont des nombres significatifs conduisant?
    • sont en tête / fuite souligne un sens?
    • etc.
  2. élaborer des règles et un algorithme pour convertir un nom dans un répertoire (Léon est un très bon départ)

  3. lire les noms et les traiter un à la fois

    • J'utiliser une combinaison de opendir et récursivité
    • Je les copier comme vous les processus; nouveau poste de Leon est un excellent exemple
  4. si ce script devra être maintenu et utilisé à l'avenir, je voudrais créer defintely des tests (par exemple en utilisant http://search.cpan.org/dist/Test-More/ ) pour chaque trajet d'expressions rationnelles; lorsque vous trouvez une nouvelle ride, ajouter un nouveau test et assurez-vous qu'il échoue, fixer le regex, puis ré-exécuter le test pour vous assurer que rien cassé

Je n'ai pas utilisé Perl dans un certain temps, donc je vais écrire cela en Ruby. Je commenterai à établir une pseudocode.

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   

C'est l'idée, de toute façon - je me suis assuré tous les appels API sont corrects, mais ce n'est pas le code testé. Est-ce que cela ressemble à ce que vous essayez d'accomplir? Serait-ce vous aider à écrire le code en Perl?

Vous pouvez diviser les noms de fichiers en utilisant quelque chose comme

@tokens = split /_+/, $filename

La dernière entrée de @tokens devrait être ".txt" pour tous ces noms de fichiers, mais la deuxième à la dernière devrait être similaire pour la même personne dont le nom a été mal orthographié dans des lieux (ou « Dr Jones » changé en « Brian Jones " par exemple). Vous pouvez utiliser une sorte de href="http://en.wikipedia.org/wiki/Edit_distance" en mesure de similarité pour comparer @tokens[-2] pour divers noms de fichiers ; lorsque deux entrées ont assez des noms similaires derniers, ils devraient vous demander en tant que candidat à la fusion.

Comme vous posez une question très générale , toute langue pourrait le faire aussi longtemps que nous avons une meilleure codification des règles. Nous n'avons même pas les détails , seul un « échantillon ».

Alors, aveugle de travail, il semble que la surveillance humaine sera nécessaire. Donc, l'idée est un tamis . Quelque chose que vous pouvez à plusieurs reprises courir et vérifier et exécuter à nouveau et vérifier encore et encore jusqu'à ce que vous avez tout à trier quelques petites tâches manuelles.

Le code ci-dessous marques beaucoup d'hypothèses , parce que vous nous assez bien laissé à la manipuler. Dont un est que l'échantillon est une liste de tous les noms possibles derniers; s'il y a d'autres noms, ajouter « em et l'exécuter à nouveau.

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 
                             )
        );
}
scroll top