Posts tagged ‘perl’

L’astuce du jour : voici comment faire des modifications de contenu en pipe ou sur des fichiers comme avec sed, mais avec la puissance des expressions régulières de perl.

En pipe (y en a marre des foo/bar !) :
perl -pe 'undef $/; s/Truc/Machin/i;'

Avec des fichiers :
perl -pe 'undef $/; s/Truc/Machin/i;' -i fichier

Exemple avec wget et un feed xml

Pour récupérer la date de dernière mise à jour d’un flux RSS :
wget -q -O - http://blog.cyril.me/feed/ | grep lastB | perl -pe 'undef $/; s/.*>(.*?)<.*/\1/'

Match et non-match…

En cette période de coupe du monde de football 2010, quoi de plus naturel que de parler de match ? … Hum.

Problématique

Mon problème aujourd’hui, c’est de sélectionner les listes qui contiennent une certaines expression régulière, mais pas une autre. Plus exactement, et pour faire dans les anglicismes, je cherche « foo.* », mais pas « foobar ».

La solution

Il faut utiliser la forme suivante : (?!regexp) pour choisir quelque chose qui ne match pas la regexp en question.

Mon exemple en pratique :

$ echo -e 'foo\nfoobar\nfoofighting' | grep -P 'foo(?!bar)'
foo
foofighting

Plus d’informations disponible avec perldoc perlre, en particulier le chapitre sur les expressions régulières étendues (Extended Patterns)

Problème

Lorsque l’on parse des fichiers, surtout les fichiers HTML, on a parfois besoin d’extraire un texte compris entre deux balises. Voici donc un moyen d’y parvenir avec les expressions régulières.

Considérations techniques

Je parlerais ici des expressions régulières perl (et par extension : grep -P et preg_* de php).

Par défaut, un /.*/ ou toute autre expression sera « gourmand » : ça matche tant que ça peut encore matcher. Par exemple, /a*/ appliqué à la chaîne "aaa" va matcher "aaa", et non pas "".

Dans mon cas pratique, j’ai une balise <td id="identifier"> et j’en cherche le contenu. Donc si la regexp utilisée est /<td id="identifier">(.*)<\/td>/ alors je vais matcher ce qu’il y a entre mon td ouvrant, et le dernier td fermant.

La solution

La solution consiste à dire au moteur d’expression régulière de prendre le moins possible, et ça se code comme suit : /<td id="identifier">(.*?)<\/td>/.

Bibliographie : http://social.msdn.microsoft.com/Forums/en-US/regexp/thread/ab975ba5-31dd-4e6d-b72f-5cd6bf374b02 (et oui, parfois on trouve des choses utiles sur les forums de microsoft).

Les arrondis peuvent parfois réserver des surprises. Regardez par exemple le code suivant :

  1. <?php
  2. for ($i = 0; $i <= 1; $i += 0.05)
  3. {
  4.   printf ("%.2f %.1f %.1f\n", $i, $i, round($i, 1));
  5. }
  6. ?>

Ce qui nous donne :

0.00 0.0 0.0
0.05 0.1 0.1
0.10 0.1 0.1
0.15 0.2 0.2
0.20 0.2 0.2
0.25 0.2 0.3
0.30 0.3 0.3
0.35 0.3 0.4
0.40 0.4 0.4
0.45 0.4 0.5
0.50 0.5 0.5
0.55 0.5 0.6
0.60 0.6 0.6
0.65 0.7 0.7
0.70 0.7 0.7
0.75 0.8 0.8
0.80 0.8 0.8
0.85 0.9 0.9
0.90 0.9 0.9
0.95 1.0 1.0

Les deux résultats sont différents !

L’explication, vous la trouverez dans la documentation de perl (perldoc -q round) :

Don’t blame Perl.  It’s the same as in C.  IEEE says we have to do this.  Perl numbers whose absolute values are integers under 2**31 (on 32 bit machines) will work pretty much like mathematical integers.  Other numbers are not guaranteed.

Premier article: comment matcher un nom de domaine avec une expression régulière sur une adresse web, ou bien comment vérifier qu’une adresse web est bien celle d’un nom de domaine spécifié.

Alors voici l’expression régulière que je propose:

^https?://([^/]*\.)?\Qcyril.me\E(:\d+)?(\z|/)

Expications:

  • ^ : permet de signifier que l’on commence au début de la chaine de caractères (on ne veut pas matcher une adresse du type (http://redir.org/?url=http://cyril.me/)
  • https? : on ne match que les protocoles web, soit http ou https
  • ([^/]*\.)?\Qcyril.me\E : match toutes les possibilités de a.b.c.cyril.me, ou cyril.me tout court. L’important est qu’il n’y ai pas de ‘/’ avant le cyril.me. Attention: le but n’est pas de vérifier la conformité d’une adresse web ou du nom de domaine, car on part du principe que l’adresse est valide. Le \Q permet de rechercher ce qui suit sans en interpréter la signification (en l’occurrence, ça permet de ne pas interpréter le ‘.’ comme « n’importe quel caractère »). Le \E repasse en mode d’interprétation.
  • (:\d+)? : parfois, on est sur un port différent que le port standard, par exemple cyril.me:81 ou ou même sur le port normal cyril.me:80
  • (/|\z) : Soit tout ce qui précède est suivi par un ‘/’, puis suivent n’importent quels caractères qui ne nous intéressent pas, ou bien on match la fin de la chaine par \z.

Voici un petit code qui permet de tester cette expression régulière sur plusieurs adresses (cf aussi une page web pour faire des tests):

  1. #!/usr/bin/perl
  2.  
  3. # Differentes regexps
  4. @url = ("http://cyril.me:80/abc", "http://cyril.me/abc", "https://www.cyril.me/abc", "http://cyril.me", "https://wwwcyril.me/abc", "http://www.cyril.mercredi/");
  5. # l'expression reguliere (attention aux echappements)
  6. $r = "^https?://([^/]+\\.)?cyril.me(:\\d+)?(/|\\z)";
  7.  
  8. $u (@url) {
  9.   ($u =~ /$r/i) {
  10.     "$u matches\n";
  11.   }
  12.   {
  13.     "$u not matches\n";
  14.   }
  15. }