2013-02-05 2 views
1

Je me suis retrouvé à écrire PERL pour la première fois depuis environ 8 ans et j'ai des difficultés avec quelque chose qui devrait être facile. Voici la prémisse de base:Effectuer une expression régulière différente pour chaque colonne dans un fichier délimité par des tabulations

Un fichier contenant une centaine de champs dont 10 ont des données incorrectes (le O de des 0)

A B C D E F ... 
br0wn red 1278076 0range "20 tr0ut" 123 ... 
Green 0range 90876 Yell0w "18 Salm0n" 456 ... 

Je suis en train d'écrire le programme pour diviser les champs, puis permettez-moi d'exécuter une regex sur le champ A pour remplacer 0 par O mais pas remplacer 0 par O pour la colonne C et ainsi de suite J'ai le problème supplémentaire de devoir éventuellement exécuter une autre regex pour la colonne E par exemple.

J'ai été capable de diviser tous les champs d'un enregistrement par le/t. J'ai un problème de mise en forme de ma commande pour parcourir chaque champ et exécuter une expression rationnelle spécifique en fonction du champ.

Toute aide serait appréciée et je vous Paypal 10 dollars pour une boisson de votre choix si vous le résoudre.

+0

Comment voulez-vous gérer la colonne E? '20 tr0ut' – TLP

+0

C'est juste que, je pensais que je traduirais seulement 0 à O où pas les deux prochains numéros. Je pense que cela attraperait la plupart d'entre eux – user2041477

Répondre

0

Voici une façon d'utiliser GNU awk. Ajoutez simplement les noms de colonne dans le tableau dans le bloc BEGIN. Dans l'exemple ci-dessous, seules les colonnes A, C et E seront modifiées. Exécuter comme:

awk -f script.awk file 

Contenu de script.awk:

BEGIN { 
    FS=OFS="\t" 

    a["A"] 
    a["C"] 
    a["E"] 
} 

{ 
    for (i=1;i<=NF;i++) { 

     if ($i in a && NR==1) { 
      b[i] 
     } 

     else if (i in b) { 
      $i = gensub(/(^|[^0-9])0([^0-9]|$)/,"\\1o\\2", "g", $i) 
     } 
    } 
}1 

Tab séparés résultats:

A B C D E F ... 
brown red 1278076 0range "20 trout" 123 ... 
Green 0range 90876 Yell0w "18 Salmon" 456 ... 

Sinon, voici le one-liner:

awk 'BEGIN { FS=OFS="\t"; a["A"]; a["C"]; a["E"] } { for (i=1;i<=NF;i++) { if ($i in a && NR==1) b[i]; else if (i in b) $i = gensub(/(^|[^0-9])0([^0-9]|$)/,"\\1o\\2", "g", $i) } }1' file 
+0

Wow réponse rapide, Alors je pensais le faire avec awk mais je voulais être en mesure de faire des règles différentes basées sur le nom de domaine comme A, B et non par la position du champ. Je ne le nom restera le même pas nécessairement l'emplacement du champ – user2041477

+0

@ user2041477 Êtes-vous maintenant en ajoutant l'exigence que les noms de champs, et non les positions, doivent être utilisés? – TLP

+0

Les noms de champs seraient optimaux, Désolé mon système n'a pas TEXT :: CSV Je le mets dans ma bibliothèque locale. – user2041477

0

Créer un tableau de , quelque chose comme des sous-routines:

my @fixer; 
$fixer[0] = sub { $_[0] =~ s/0/o/; }; 
my @fields = split /\t/, $input; 
for (my $i = 0; $i <= $#fields; $i++) { 
    $fixer[$i]->($fields[$i]) if defined $fixer[$i]; 
} 
+0

Donc peut-être que je ne lis pas cela correctement, mais voici ma traduction, la matrice de fixateur a un sous-programme et je pourrais y ajouter des sous-routines supplémentaires. Puis je diviser les champs et pour chaque champ exécuter le fixateur de champ? $ fixeur [$ i] -> ($ fields [$ i]) si défini $ fixateur [$ i]; – user2041477

1

l'aide d'un analyseur de csv, comme Text::CSV est pas compliqué. Quelque chose comme cela pourrait suffire:

use strict; 
use warnings; 
use Text::CSV; 

my $csv = Text::CSV->new({ 
     sep_char => "\t", 
     binary  => 1, 
     eol   => $/, 
}); 
while (my $row = $csv->getline(*DATA)) { 
    tr/0/o/ for @{$row}[0, 1, 3];   # replace in cols A, B and D 
    s/(?<!\d)0(?!\d)/o/g for @{$row}[4];  # replace in col E 
    $csv->print(*STDOUT, $row);    # print the result 
} 


__DATA__ 
A B C D E F 
br0wn red 1278076 0range "20 tr0ut" 123 
Green 0range 90876 Yell0w "18 Salm0n" 456 

Sortie:

A  B  C  D  E  F 
brown red  1278076 orange "20 trout"  123 
Green orange 90876 Yellow "18 Salmon"  456 

Notez que j'ai traité votre chaîne mixte (colonne E) avec une regex simpliste au lieu de translittération (remplacer global), et simplement ne remplace pas les zéros qui sont à côté des nombres, qui échoueront pour certains nombres, tels que 20.0 ou 0.

Mise à jour:

Si vous voulez faire les substitutions basées sur la colonne noms au lieu de la position, les choses deviennent un peu plus compliqué. Cependant, Text::CSV peut le gérer.

use strict; 
use warnings; 
use Text::CSV; 

my @pure_text = qw(A B D); 
my @mixed  = qw(E); 

my $csv = Text::CSV->new({ 
     sep_char => "\t", 
     binary  => 1, 
     eol  => $/, 
}); 

my $cols = $csv->getline(*DATA);    # read column names 
$csv->print(*STDOUT, $cols); 
$csv->column_names($cols);     # set column names 

while (my $row = $csv->getline_hr(*DATA)) { # hash ref instead of array ref 
    tr/0/o/ for @{$row}{@pure_text};   # substitution on hash slice 
    s/(?<!\d)0(?!\d)/o/g for @{$row}{@mixed}; 
    my @row = @{$row}{@$cols};    # make temp array for printing 
    $csv->print(*STDOUT, \@row); 
} 


__DATA__ 
A B C D E F 
br0wn red 1278076 0range "20 tr0ut" 123 
Green 0range 90876 Yell0w "18 Salm0n" 456 

Ce code est autonome pour la démonstration.Pour essayer le code sur un fichier, changer *DATA-*STDIN et utiliser le script comme suit:

perl script.pl < input.csv 
+0

On dirait que ça pourrait faire l'affaire, la sortie imprime des "caractères autour des résultats ou est-ce juste un remplacement du caractère de tabulation? Je vais construire mes tableaux pour séparer les champs et lui donner une vraie course. Je vous dois un paypal – user2041477

+0

Le module csv ajoute des guillemets quand ils sont nécessaires, je ne sais pas de quoi vous parlez Hey, ce serait le premier argent que je fais perl de codage. :) – TLP

+0

Donc, voici un étrange, je J'ai essayé de l'exécuter contre un vrai fichier et j'ai enregistré 30 enregistrements et ça a cassé Je pense que ce n'est pas une analyse correcte de la tabulation Il y a une longue chaîne avec plusieurs guillemets dans la chaîne qui semble la casser. – user2041477

0

je serais probablement utiliser Perl en mode 'autosplit':

perl -a -p -F"\t" \ 
    -e '$F[0] =~ s/0/o/g; 
     $F[1] =~ s/0/O/g; 
     $F[3] =~ s/0/o/g; 
     $F[4] =~ s/(\D)0(\D)/\1o\2/g; # Or other more complex regex 
     # ...       # Other fields can be edited 
     $_ = join("\t", @F);   # Reassign fields to $_ 
     ' data-file 

Le regex pour $F[4] changements '20 tr0ut' en '20 truites '; vous pouvez le rendre plus complexe si vous avez besoin.

sortie sur les échantillons:

A  B  C  D  E  F  ... 
brown red  1278076 orange "20 trout"  123  ... 
Green Orange 90876 Yellow "18 Salmon"  456  ... 

Cela ne suppose un fichier de données strictement séparées par des tabulations. Les chaînes entre guillemets contenant des espaces compliquent les choses si vous n'avez pas de données strictement séparées par des tabulations; À ce stade, Text :: CSV est attrayant pour lire les lignes.

0

est ici une façon avec une configuration simple en utilisant des références de tableau et/ou sous-routines, puis les substitutions qui se passe plus tard:

use strict; 
use warnings; 

my @subst = ([ 
    ['this', 'that'], 
    ['O', 1], 
],[ 
    ['foo', 'boo'], 
    sub {s/a.*//}, 
]); 

sub mk_subst { 
    my $list = shift; 
    my ($this, $that) = eval { @$list }; 
    return $list unless defined $this; 
    sub { s/\Q$this/$that/ }; 
} 

my @all; 
for my $set (@subst) { 
    my @list = eval { @$set }; 
    unless (@list) { 
    push @all, [ sub {} ]; 
    next; 
    } 
    my @re; 
    for my $s (@list) { 
    push @re, mk_subst($s); 
    } 
    push @all, \@re; 
} 

while (<DATA>) { 
    chomp; 
    my @list = split /\t/, $_, -1; 
    for my $i (0..$#list) { 
    for ($list[$i]) { 
     for my $funcs ($all[$i]) { 
     for my $f (@$funcs) { 
      $f->(); 
     } 
     } 
    } 
    } 
    print join("\t", @list), "\n"; 
} 

__DATA__ 
thisO fooabca1234 
abc 123fooabca1234 
+0

J'ai essayé celui-ci et il semble imprimer 2 enregistrements pour chaque article – user2041477

+0

C'est parce que j'ai deux instructions d'impression dans la boucle ... une transformation avant et après ... débogage, je prends la première impression. – runrig

0
perl -F -lane 'for(@F){$_=~s/0/o/g if(/0/ && /[a-zA-Z]+/);} print "@F"' your_file 

Testée ci-dessous

> cat temp 
br0wn red 1278076 0range "20 tr0ut" 123 ... 
Green 0range 90876 Yell0w "18 Salm0n" 456 ... 

> perl -F -lane 'for(@F){$_=~s/0/o/g if(/0/ && /[a-zA-Z]+/);} print "@F"' temp 
brown red 1278076 orange "20 trout" 123 ... 
Green orange 90876 Yellow "18 Salmon" 456 ... 
> 
Questions connexes