[Perl] simplifier ma regex

simplifier ma regex [Perl] - Perl - Programmation

Marsh Posté le 05-12-2015 à 17:42:50    

Hello,
 
Voici ma ligne à parser:

Code :
  1. 2015/12/05 12:55:56.217 RmCmdSuccess,Cmd=CreateSession,RID=172.21.205.31,CmdTime=7ms,SesId=0025643d7ecb/269123690,chan0=22,MPN=274,BW=2475007,GDA=0.0.0.0,UDP=56100,Tid=447885331


 
Mon but est d'obtenir ça:

Code :
  1. 20151205125556|0025643d7ecb/269123690|2475007|0.0.0.0|LOG-01


 
J'y arrive avec ce code ci-dessous

Code :
  1. if (/Cmd=CreateSession/) {
  2.        print $_, "\n";
  3.        $_ =~ s/^([^R]*)\ ([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*)(.*)$/$1|$6|$9|$10|LOG-01/g;
  4.        $_ =~ s/SesId=|BW=|GDA=|:|\ //g;
  5.        my ($startdate, $session, $bitrate, $gda, $srm, $mls) = split(/\|/, $_);
  6.        $startdate =~ s/\///g;
  7.        ($startdate, $mls) = split(/\./, $startdate);
  8.        print "$startdate|$session|$bitrate|$gda|$srm", "\n";
  9.        last;
  10. }


 
Mais je ne trouve pas ma regex super jolie et je voudrai arriver à récupérer mes variables sans les nettoyer (ce que je fais en ligne 4. et 6. et 7.)
Est-ce qu'il est possible de faire plus simple et plus propre?
 
Merci d'avance.

Reply

Marsh Posté le 05-12-2015 à 17:42:50   

Reply

Marsh Posté le 06-12-2015 à 12:51:58    

Voila une version plus directe, au vu de tes données:

Code :
  1. #!/usr/bin/perl
  2. use Modern::Perl;
  3.  
  4. $_ = '2015/12/05 12:55:56.217 RmCmdSuccess,Cmd=CreateSession,RID=172.21.205.31,CmdTime=7ms,';
  5. $_ .= 'SesId=0025643d7ecb/269123690,chan0=22,MPN=274,BW=2475007,GDA=0.0.0.0,UDP=56100,Tid=447885331';
  6. my $result = '20151205125556|0025643d7ecb/269123690|2475007|0.0.0.0|LOG-01';
  7.  
  8. if (m|^(\d\d\d\d)/(\d\d)/(\d\d)\s(\d\d):(\d\d):(\d\d)\.\d+.+,Cmd=CreateSession,.+,SesId=([^,]+),.+,BW=([^,]+),GDA=([^,]+),|) {
  9.  print "$1$2$3$4$5$6|$7|$8|$9|LOG-01\n";
  10.  print "$result\n"; # pour comparer
  11. }


 
J'ai choisi |...| pour encadrer la regex plutôt que l'habituel /.../ parce qu'il y a des / dans la chaîne à parser (et que je suppose qu'il n'y aura pas de | dans cette chaîne).
 
si tu veux que ta regexp soit plus lisible, avec le modifier x, on peut la mettre sur plusieurs lignes:

Code :
  1. if (m|^(\d\d\d\d)/(\d\d)/(\d\d)\s(\d\d):(\d\d):(\d\d)\.\d+.+,
  2.        Cmd=CreateSession,
  3.        .+,
  4.        SesId=([^,]+),
  5.        .+,
  6.        BW=([^,]+),
  7.        GDA=([^,]+),|x) {
  8.  print "$1$2$3$4$5$6|$7|$8|$9|LOG-01\n";
  9.  print "$result\n"; # pour comparer
  10. }


 
Mais bon, une manière bien plus logique de procéder est la suivante:

Code :
  1. #!/usr/bin/perl
  2. use Modern::Perl;
  3.  
  4. $_ = '2015/12/05 12:55:56.217 RmCmdSuccess,Cmd=CreateSession,RID=172.21.205.31,CmdTime=7ms,';
  5. $_ .= 'SesId=0025643d7ecb/269123690,chan0=22,MPN=274,BW=2475007,GDA=0.0.0.0,UDP=56100,Tid=447885331';
  6. my $result = '20151205125556|0025643d7ecb/269123690|2475007|0.0.0.0|LOG-01';
  7.  
  8. if (/Cmd=CreateSession/) {
  9.  my @fields = split /,/;
  10.  my $time = shift @fields;
  11.  $time =~ s|^(\d\d\d\d)/(\d\d)/(\d\d)\s(\d\d):(\d\d):(\d\d)\.\d+.+|$1$2$3$4$5$6|o;
  12.  my %hash = map {if (/(.+)=(.+)/) {($1, $2)}} @fields;
  13.  print "$time|$hash{SesId}|$hash{BW}|$hash{GDA}|LOG-01\n";
  14.  print "$result\n";
  15. }


Si on repère une ligne à traiter: if (/Cmd=CreateSession/)
On splitte les champs selon la virgule: my @fields = split /,/;
Ils sont tous de la forme XX=YYY sauf le premier.
On colle le premier champ dans une variable a part: my $time = shift @fields;
On colle les autres dans un hash, un champ de la forme XX=YYY fournissant un élment du hash de clé XX et de valeur YYY: my %hash = map {if (/(.+)=(.+)/) {($1, $2)}} @fields;
On extrait du premier champ de qui nous intéresse: $time =~ s|^(\d\d\d\d)/(\d\d)/(\d\d)\s(\d\d):(\d\d):(\d\d)\.\d+.+|$1$2$3$4$5$6|o;
qu'on envoie en sortie avec les valeurs du hash pour certaines clés: print "$time|$hash{SesId}|$hash{BW}|$hash{GDA}|LOG-01\n";
et c'est tout, zou!
 
Note:
J'ai écrit
my %hash = map {if (/(.+)=(.+)/) {($1, $2)}} @fields;
ça marche parce que je suis certain que tous les champs sont de la forme XX=YYY
Si je n'en suis pas sur, faire
my %hash;
map {if (/(.+)=(.+)/) {$hash{$1}=$2} @fields;
qui marche à tout les coups et ne tient compte que des champs de la bonne forme XX=YYY et saute ceux qui sont d'une autre forme.
En y repensant, un  
my %hash = grep /./, map {if (/(.+)=(.+)/) {($1, $2)}} @fields;  
fera la même chose (les champs d'une autre forme, qui renvoient undef avec map sont éliminés par le grep) et est un one liner.
 
Après, si on veut faire plus compact et moins lisible et maintenable, on peut toujours faire tout avec un hash unique:

Code :
  1. if (/Cmd=CreateSession/) {
  2.  my %hash = map {if (/(.+)=(.+)/) {($1, $2)}
  3.           elsif (m|^(\d\d\d\d)/(\d\d)/(\d\d)\s(\d\d):(\d\d):(\d\d)\.\d+.+|) {('TimeStamp', "$1$2$3$4$5$6" )}} split /,/;
  4.  print "$hash{TimeStamp}|$hash{SesId}|$hash{BW}|$hash{GDA}|LOG-01\n";
  5.  print "$result\n";
  6. }


 
On peut rendre ça encore plus compact

Code :
  1. #!/usr/bin/perl
  2. use Modern::Perl;
  3.  
  4. $_ = '2015/12/05 12:55:56.217 RmCmdSuccess,Cmd=CreateSession,RID=172.21.205.31,CmdTime=7ms,';
  5. $_ .= 'SesId=0025643d7ecb/269123690,chan0=22,MPN=274,BW=2475007,GDA=0.0.0.0,UDP=56100,Tid=447885331';
  6. my $result = '20151205125556|0025643d7ecb/269123690|2475007|0.0.0.0|LOG-01';
  7.  
  8. my $date_regex = qr|^(\d\d\d\d)/(\d\d)/(\d\d)\s(\d\d):(\d\d):(\d\d)\.\d+.+|;
  9.  
  10. if (/Cmd=CreateSession/) {
  11.  print join('|', grep(/./, map {if (/^(SesId|BW|GDA)=(.+)/o) {"$2"}
  12.                      elsif (m/$date_regex/o) {"$1$2$3$4$5$6"}} split(/,/, $_))), "|LOG-01\n";
  13.  print "$result\n";
  14. }


 
Et si on veut, quitte a laisser tomber $result et a se placer dans le cadre de ton utilisation réelle (lecture en boucle ligne à ligne d'un log), on peut même aboutir à un one liner, guru-level illisible.
 

Code :
  1. my $timestamp = qr|^(\d\d\d\d)/(\d\d)/(\d\d)\s(\d\d):(\d\d):(\d\d)\.\d+.+|;
  2. ...
  3. while (<$fh> ) {
  4.    print join('|', grep(/./, map {if (/^(SesId|BW|GDA)=(.+)/o) {"$2"}
  5.                                    elsif (m/$timestamp/o) {"$1$2$3$4$5$6"}}
  6.                                  split(/,/, $_))), "|LOG-01\n" if (/Cmd=CreateSession/);
  7. }


 
En ce qui me concerne, je ferais ceci:

Code :
  1. my $date_regex = qr{(\d\d\d\d)/(\d\d)/(\d\d)\s(\d\d):(\d\d):(\d\d)\.\d+};
  2. ...
  3. if (/Cmd=CreateSession/) {
  4.  my ($timestamp, @fields) = split /,/;
  5.  $timestamp =~ s/^$date_regex .*/$1$2$3$4$5$6/o;
  6.  my %hash = grep /./, map {if (/(.+)=(.+)/) {($1, $2)}} @fields;
  7.  print "$timestamp|$hash{SesId}|$hash{BW}|$hash{GDA}|LOG-01\n";
  8. }


Parce que c'est lisible, compréhensible (pour qui pratique un peu Perl), et surtout, flexible et réutilisable: le jour ou tu veux imprimer une autre valeur de champ en sortie, par exemple celui associé à MPN, il te suffira de coller $hash{MPN} dans le print.
 
A+,


Message édité par gilou le 06-12-2015 à 16:02:51

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
Reply

Marsh Posté le 06-12-2015 à 19:42:01    

Waouh !
Ça c'est de la réponse qui envoie du lourd  
Chapeau bas Mr Gilou  :jap:  
 
C'est très clair et en plus j'apprends des nouvelles choses  :)  
 
Merci

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed