#1 Le 22/01/2011, à 20:37
- pode
[Résolu] Problème avec une regex perl multi-ligne
Bonjour,
J'ai des fichiers XML que je souhaite modifier, mais je n'ai pas envie d'utiliser xsltproc. Je voudrais faire ça avec perl, sed ou awk (cela me permet d'approfondir mes connaissances sur ces outils).
Le pattern à rechercher est sur plusieurs lignes.
Voici deux fichiers d'exemple :
$ cat essai.xml
<!--premier groupe -->
<BaliseA nom="NOM1">
<BaliseB type="TYPE1"/>
<BaliseC sorte="SORTE1"/>
</BaliseA>
<!--deuxieme groupe -->
<BaliseA nom="NOM2">
<BaliseB type="TYPE2"/>
<BaliseC sorte="SORTE2"/>
</BaliseA>
$ cat essai.greedy.xml
<!--premier groupe -->
<BaliseA nom="NOM1">
<BaliseB type="TYPE1"/>
<BaliseC sorte="SORTE1"/>
</BaliseA>
<!--deuxieme groupe -->
<BaliseA nom="NOM2">
<BaliseB type="TYPE2"/>
<BaliseC sorte="SORTE2"/>
</BaliseA>
<!--troisieme groupe -->
<BaliseA nom="NOM3">
<BaliseB type="TYPE1"/>
<BaliseC sorte="SORTE3"/>
</BaliseA>
Le pattern à rechercher est : avoir sur une ligne BaliseA nom="NOM1", et sur la ligne suivante type="TYPE1". Il faut alors remplacer TYPE1 par TYPE4.
Je suis arrivé à écrire une commande sed, mais elle ne marche que sur le premier fichier ; sur le second fichier, ça ne marche pas (c'est le groupe NOM3 qui a été modifié) car les regex sed sont uniquement gloutonnes :
$ cat sed-multi-line-essai.sh
sed -n -e '1h;1!H;${;g;s/\(<BaliseA nom="NOM1.*<BaliseB type="\)\(TYPE1\)\("\/>\)/\1TYPE4\3/g;p;}' essai.xml > essai.sed.xml
diff -u essai.xml essai.sed.xml
sed -n -e '1h;1!H;${;g;s/\(<BaliseA nom="NOM1.*<BaliseB type="\)\(TYPE1\)\("\/>\)/\1TYPE4\3/g;p;}' essai.greedy.xml > essai.sed.greedy.xml
diff -u essai.greedy.xml essai.sed.greedy.xml
$ ./sed-multi-line-essai.sh
--- essai.xml 2011-01-22 20:10:00.066669008 +0100
+++ essai.sed.xml 2011-01-22 20:28:32.442668804 +0100
@@ -1,6 +1,6 @@
<!--premier groupe -->
<BaliseA nom="NOM1">
- <BaliseB type="TYPE1"/>
+ <BaliseB type="TYPE4"/>
<BaliseC sorte="SORTE1"/>
</BaliseA>
--- essai.greedy.xml 2011-01-22 20:09:32.070668698 +0100
+++ essai.sed.greedy.xml 2011-01-22 20:28:32.446669093 +0100
@@ -12,7 +12,7 @@
<!--troisieme groupe -->
<BaliseA nom="NOM3">
- <BaliseB type="TYPE1"/>
+ <BaliseB type="TYPE4"/>
<BaliseC sorte="SORTE3"/>
</BaliseA>
Je suis passé à perl, car perl peut être non glouton grâce à ?, mais je n'arrive pas à faire marcher le multi-ligne (/s final) + le non glouton :
$ cat ./perl-multi-line-essai.sh
perl -p -e 's/(<BaliseA nom="NOM1)(.*?)(TYPE1)/$1$2TYPE4/s' essai.xml > essai.perl.xml
diff -u essai.xml essai.perl.xml
perl -p -e 's/(<BaliseA nom="NOM1)(.*?)(TYPE1)/$1$2TYPE4/s' essai.greedy.xml > essai.perl.greedy.xml
diff -u essai.greedy.xml essai.perl.greedy.xml
$ ./perl-multi-line-essai.sh
$
Pas de différence entre avant et après.
Voyez-vous ce qui cloche ?
Dernière modification par pode (Le 23/01/2011, à 10:46)
Hors ligne
#2 Le 22/01/2011, à 21:32
- JoelS
Re : [Résolu] Problème avec une regex perl multi-ligne
En perl, tu peux définir le séparateur de 'ligne' comme une regexp. Ensuite le contenu de ton fichier sera lu 'ligne' à 'ligne', mais du point de vue de perl, c.a.d. que chaque 'ligne' lue commencera après le séparateur, et se terminera avant le suivant. Par défaut, le séparateur est le retour-chariot, donc une 'ligne' lue correspond bien à ce qu'on pense.
Mais si tes blocs de données sont séparés par une chaîne bien définie, alors tu peux lire et mettre en mémoire un seul bloc complet à chaque lecture. Ce bloc peut alors contenir des retour-chariot.
Ensuite, avec les modificateurs /m et/ou /s dans les regexp, tu peux traiter cette 'ligne' en traitant les retour-chariot comme des caractères normaux, ou pas, suivant ce que tu veux faire.
Dans un premier temps, il faut AMHA lire le bloc XML comme une seule ligne. Regardes du côté de la variable perl $/
Hors ligne
#3 Le 23/01/2011, à 03:47
- ehmicky
Re : [Résolu] Problème avec une regex perl multi-ligne
Salut,
Sur ton idée de départ, pour éviter que le .* ne soit trop gourmand, et ne mange qu'un seul newline : [^\n]*\n[^\n]* :
sed -n '1h;1!H;${g;s_\(<BaliseA nom="NOM1"[^\n]*\n[^\n]*type="\)TYPE1\("/>\)_\1TYPE4\2_g;p}' un.txt
Sinon aussi :
sed '/BaliseA nom="NOM1"/ {N;s/type="TYPE1"/type="TYPE4"/}' FICHIER
Dernière modification par ehmicky (Le 23/01/2011, à 03:57)
Stego++, bibliothèque libre de stéganographie (avec cryptographie), à venir !
Besoin de votre aide :
Stats sur les compilateurs C++ les plus utilisés
Comment utiliser les archetypes C++ ?
Hors ligne
#4 Le 23/01/2011, à 10:54
- pode
Re : [Résolu] Problème avec une regex perl multi-ligne
Avec $/=""; avant s/, ça marche (car je n'ai pas de lignes vides entre <BaliseA nom="NOM1 et la ligne avec TYPE1).
Les commandes sed marchent également très bien. Je vais les garder sous le coude.
Merci pour les réponses !!!
Hors ligne
#5 Le 24/01/2011, à 08:56
- Postmortem
Re : [Résolu] Problème avec une regex perl multi-ligne
Salut,
J'ai vu que tu parlais de awk donc je me suis amusé à le faire avec :
$ awk 'BEGIN { FS="\n"; RS=" \n"; OFS=FS; ORS=RS }
$2 ~ /<BaliseA nom="NOM1">/ && $3 ~ /<BaliseB type="TYPE1"/ { gsub("TYPE1","TYPE4",$3) }
{ print }' essai.greedy.xml > essai.awk.greedy.xml
$ diff essai.greedy.xml essai.awk.greedy.xml
3c3
< <BaliseB type="TYPE1"/>
---
> <BaliseB type="TYPE4"/>
17a18
>
Dernière modification par Postmortem (Le 24/01/2011, à 08:56)
Mot' a dit : « Un Hellfest sans Slayer, c'est comme une galette-saucisse sans saucisse ! »
Hors ligne