Effectuer une expression régulière différente pour chaque colonne d’un fichier délimité par des tabulations

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 le principe de base:

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

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

J’essaie d’écrire le programme pour séparer les champs et ensuite me permettre d’exécuter une expression régulière sur le champ A pour remplacer 0 par O mais ne pas remplacer 0 par O pour la colonne C, etc. expression régulière alternée pour la colonne E par exemple.

J’ai pu séparer tous les champs d’un enregistrement par le / t. J’ai un problème de formatage de ma commande pour parcourir chaque champ et exécuter une expression spécifique basée sur le champ.

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

Utiliser un parsingur csv tel que Text::CSV n’est pas compliqué. Quelque chose comme cela pourrait suffire:

 use ssortingct; 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/(?print(*STDOUT, $row); # print the result } __DATA__ ABCDEF br0wn red 1278076 0range "20 tr0ut" 123 Green 0range 90876 Yell0w "18 Salm0n" 456 

Sortie:

 ABCDEF brown red 1278076 orange "20 trout" 123 Green orange 90876 Yellow "18 Salmon" 456 

Notez que j’ai manipulé votre chaîne de caractères mixte (colonne E) avec une expression rationnelle simpliste au lieu de la translittération (remplacement global), et qu’elle ne remplace pas les zéros situés à côté des nombres, qui échouent pour

Mettre à jour:

Si vous voulez effectuer les substitutions en fonction des noms de colonne plutôt que de la position, les choses se compliquent un peu. Cependant, Text::CSV peut le gérer.

 use ssortingct; use warnings; use Text::CSV; my @pure_text = qw(ABD); 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/(?print(*STDOUT, \@row); } __DATA__ ABCDEF 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, changez *DATA en *STDIN et utilisez le script comme suit:

 perl script.pl < input.csv 

Créez un tableau de sous-routines, par exemple:

 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]; } 

J’utiliserais probablement 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 

L’expression rationnelle pour $F[4] modifie ’20 tr0ut ‘en ’20 truite’; vous pouvez le rendre plus complexe si vous en avez besoin.

Sortie sur des données d’échantillon:

 ABCDEF ... brown red 1278076 orange "20 trout" 123 ... Green Orange 90876 Yellow "18 Salmon" 456 ... 

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

Voici un moyen d’utiliser GNU awk . Ajoutez simplement les noms de colonne dans le tableau du 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 

Résultats séparés par des tabulations:

 ABCDEF ... brown red 1278076 0range "20 trout" 123 ... Green 0range 90876 Yell0w "18 Salmon" 456 ... 

Alternativement, 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 
 perl -F -lane 'for(@F){$_=~s/0/o/g if(/0/ && /[a-zA-Z]+/);} print "@F"' your_file 

Testé 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 ... > 

Voici une manière simple avec une configuration simple utilisant des références de tableau et / ou des sous-programmes, puis les substitutions se produisant plus tard:

 use ssortingct; 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 () { 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