\w reconnaisse les caractères nationaux?
/[a-zA-Z]/?
/o?
\G dans une expression régulière ?
perlfaq6 - Expressions Régulières (Revision: 1.22, Date: 1998/07/16 14:01:07)
Cette section est incroyablement courte car les autres sont parsemées de réponses concernant des expressions régulières. Par exemple, décoder une URL et vérifier si quelque chose est un nombre ou non est du domaine des expressions régulières, mais ces réponses existent ailleurs dans la FAQ (dans le chapitre sur les Données et le Réseau pour l'exemple donné).
Trois méthodes peuvent rendre les expressions régulières faciles à maintenir et compréhensibles.
Décrivez ce que vous faites et comment vous le faites en utilisant la façon de commenter habituelle de Perl.
# transforme la ligne en le premier mot, le signe deux points # et le nombre de caractères du reste de la ligne s/^(\w+)(.*)/ lc($1) . ":" . length($2) /meg;
En utilisant le modificateur /x, les espaces seront ignorés dans le motif de l'expression régulière
(excepté dans un regroupement de caractères), et vous pourrez aussi
utiliser les commentaires habituels dedans. Comme vous pouvez l'imaginer,
espaces et commentaires aident pas mal.
/x vous permet de transformer ceci:
s{<(?:[^>'"]*|".*?"|'.*?')+>}{}gs;
en:
s{ < # signe inférieur
(?: # parenthèse ouvrante ne faisant pas de référence
[^>'"] * # 0 ou plus de caract. qui ne sont ni >, ni ' ni "
| # ou
".*?" # une partie entre guillemets (reconnaissance min)
| # ou
'.*?' # une partie entre apostrophes (reconnaissance min)
) + # tout cela se produisant une ou plusieurs fois
> # signe supérieur
}{}gsx; # remplacé avec rien, cad supprimé
Ce n'est pas encore aussi clair que de la prose, mais c'est très utile pour décrire le sens de chaque partie du motif.
Bien que nous pensons habituellement que les motifs sont délimités par le
caractère /, ils peuvent être délimités par presque n'importe quel caractère. la page de manuel perlre l'explique. Par exemple, s/// ci dessus utilise des accolades comme délimiteurs. Sélectionner un autre
délimiteur permet d'éviter de le quoter à l'intérieur du motif:
s/\/usr\/local/\/usr\/share/g; # mauvais choix de délimiteur s#/usr/local#/usr/share#g; # meilleur
Ou vous n'avez pas plus d'une ligne dans la chaine où vous faites la recherche (cas le plus probable), ou vous n'appliquez pas le bon modificateur à votre motif (cas possible).
Il y a plusieurs manières d'avoir plusieurs lignes dans une chaine. Si vous voulez l'avoir automatiquement en lisant l'entrée, vous initialiserez $/ (probablement à ``'' pour des paragraphes ou undef pour le fichier entier) ce qui vous permettra de lire plus d'une ligne à la fois.
Lire la page de manuel perlre vous aidera à décider lequel de /s et /m (ou les deux) vous aimeriez utiliser: /s permet au point de reconnaitre le caractère fin de ligne, et /m permet au circonflexe et dollar de reconnaître tous les débuts et fins de ligne, pas seulement le début et la fin de la chaîne. Vous pouvez l'utiliser pour être sûr que vous avez bien plusieurs lignes dans votre chaine.
Par exemple, ce programme détecte les mots répétés, même s'ils sont coupés (mais pas dans plusieurs paragraphes). Pour cet exemple, nous n'avons pas besoin de /s car nous n'utilisons pas le point dans l'expression régulière dont on veut qu'elle enjambe les lignes. Nous n'avons pas besoin non plus de /m car nous ne voulons pas que le circonflexe ou le dollar fasse une reconnaissance d'un début ou d'une fin d'une nouvelle ligne. Mais il est impératif que $/ soit mis pour autre chose que l'option par défaut, ou autrement nous n'aurons pas plusieurs lignes d'un coup à se mettre sous la dent.
$/ = ''; # lis au moins un paragraphe entier,
# pas qu'une seule ligne
while ( <> ) {
while ( /\b([\w'-]+)(\s+\1)+\b/gi ) { # les mots commencent par
# des caractères alphanumériques
print "$1 est répété dans le paragraphe $.\n";
}
}
Voilà le code qui trouve les phrases commençant avec ``From '' (qui devrait être transformés par la plupart des logiciels de courrier électronique):
$/ = ''; # lis au moins un paragraphe entier, pas qu'une seule ligne
while ( <> ) {
while ( /^From /gm ) { # /m fait que ^ reconnaisse
# tous les débuts de ligne
print "from de départ dans le paragraphe $.\n";
}
}
Voilà le code qui trouve tout ce qu'il y a entre START et END dans un paragraphe:
undef $/; # lis le fichier entier, pas seulement qu'une ligne
# ou un paragraphe
while ( <> ) {
while ( /START(.*?)END/sm ) { # /s fait que . enjambe les lignes
print "$1\n";
}
}
Vous pouvez utiliser l'opérateur quelque peu exotique .. de Perl (documenté dans la page de manuel perlop):
perl -ne 'print if /START/ .. /END/' fichier1 fichier2 ...
Si vous voulez du texte et non des lignes, vous pouvez utiliser
perl -0777 -pe 'print "$1\n" while /START(.*?)END/gs' fichier1 fichier2 ...
Mais si vous voulez des occurrences imbriquées de START à l'intérieur de END, vous tombez sur le problème décrit, dans cette section, sur la
reconnaissance de texte bien équilibré.
Ici un autre exemple d'utilisation de ..:
while (<>) {
$in_header = 1 .. /^$/;
$in_body = /^$/ .. eof();
# maintenant choisissez entre eux
} continue {
reset if eof(); # fixe $.
}
$/ doit être une chaine, et non une expression régulière. Awk est au moins meilleur pour quelque chose. :-)
En fait, vous pouvez faire ceci si vous n'avez pas à vous soucier de mettre le fichier entier en mémoire:
undef $/; @records = split /your_pattern/, <FH>;
Le module Net::Telnet (disponible chez CPAN) a la capacité d'attendre pour un motif dans le flux d'entrée, ou fait un timeout si ce motif n'apparait pas dans un temps donné.
## Crée un fichier de trois lignes. open FH, ">file"; print FH "La première ligne\nLa deuxième ligne\nLa troisième ligne\n"; close FH;
## Lui met un handle de fichier en lecture/écriture. $fh = new FileHandle "+<file";
## L'attache à un objet "stream". use Net::Telnet; $file = new Net::Telnet (-fhopen => $fh);
## Cherche la deuxième ligne et imprime la troisième.
$file->waitfor('/deuxième ligne\n/');
print $file->getline;
Cela dépend de votre sens de ``préserver la casse''. Le script suivant qui fait le remplacement met la même casse, lettre après lettre, que l'original. Si la chaîne de remplacement a plus de caractères que la chaine qui est remplacée, la casse du dernier caractère de la deuxième est utilisée pour le remplacement du surplus.
# L'original est de Nathan Torkington, mis en forme par Jeffrey Friedl
#
sub preserve_case($$)
{
my ($old, $new) = @_;
my ($state) = 0; # 0 = no change; 1 = lc; 2 = uc
my ($i, $oldlen, $newlen, $c) = (0, length($old), length($new));
my ($len) = $oldlen < $newlen ? $oldlen : $newlen;
for ($i = 0; $i < $len; $i++) {
if ($c = substr($old, $i, 1), $c =~ /[\W\d_]/) {
$state = 0;
} elsif (lc $c eq $c) {
substr($new, $i, 1) = lc(substr($new, $i, 1));
$state = 1;
} else {
substr($new, $i, 1) = uc(substr($new, $i, 1));
$state = 2;
}
}
# on se retrouve avec ce qui reste de new
# (quand new est plus grand que old)
if ($newlen > $oldlen) {
if ($state == 1) {
substr($new, $oldlen) = lc(substr($new, $oldlen));
} elsif ($state == 2) {
substr($new, $oldlen) = uc(substr($new, $oldlen));
}
}
return $new;
}
$a = "ceci est un TEsT de casse"; $a =~ s/(test)/preserve_case($1, "succes")/gie; print "$a\n";
Cela affiche:
Ceci est un SUcCES de casse
\w reconnaisse les caractères nationaux?Regardez perllocale.
/[a-zA-Z]/?
Un caractère alphabétique doit être /[^\W\d_]/, quelle que soit la locale que vous utilisez. Les non-alphabétiques
doivent être /[\W\d_]/
(sous réserve que vous ne considérez pas un souligné comme une lettre).
L'analyseur syntaxique de Perl remplacera par leur valeur les occurences de
$variable et @variable dans les expressions
régulières à moins que le délimiteur soit une apostrophe. Rappelez vous
aussi que la partie droite d'une s/// substitution est considérée comme une chaine entre guillemets (voir la page de manuel perlop pour plus de détails). Rappelez vous encore que n'importe quel caractère
spécial d'expression régulière agira à moins que vous ne précédiez la
substitution avec \Q. Voici un exemple:
$string = "to die?"; $lhs = "die?"; $rhs = "sleep no more";
$string =~ s/\Q$lhs/$rhs/; # $string est maintenant "to sleep no more"
Sans le \Q, l'expression régulière aurait faussement aussi reconnu ``di''.
/o?
Utiliser une variable dans une opération de reconnaissance avec expression
régulière force une réévaluation (et peut-être une recompilation) à chaque
fois qu'elle est appliquée. Le modificateur /o verrouille l'expression régulière la première fois qu'elle est utilisée.
C'est toujours ce qui se passe avec une expression régulière constante, et
en fait, le motif est compilé dans le format interne en même temps que le
programme entier l'est.
Utiliser /o est peu pertinent à moins que le remplacement de variable soit utilisé dans
le motif, et si cela est, le moteur d'expression régulière ne prendra pas
en compte les modifications de la variable ultérieures à la toute première évaluation.
/o est souvent utilisé pour gagner en efficacité en ne faisant pas les
évaluations nécessaires quand vous savez que cela ne pose pas de problème
(car vous savez que les variables ne changeront pas), ou plus rarement,
quand vous ne voulez pas que l'expression régulière remarque qu'elles
changent.
Par exemple, voici un programme ``paragrep'':
$/ = ''; # mode paragraphe
$pat = shift;
while (<>) {
print if /$pat/o;
}
Bien que cela puisse se faire, c'est plus compliqué que vous ne pensez. Par exemple, cette simple ligne
perl -0777 -pe 's{/\*.*?\*/}{}gs' foo.c
marchera dans beaucoup de cas mais pas tous. Vous voyez, c'est un peu simplet pour certains types de programmes C, en particulier, ceux où des chaines protégées sont des commentaires. Pour cela, vous avez besoin de quelque chose de ce genre, créé par Jeffrey Friedl:
$/ = undef;
$_ = <>;
s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|\n+|.[^/"'\\]*)#$2#g;
print;
Cela pourrait, évidemment, être écrit plus lisiblement avec le modificateur /x en ajoutant des espaces et des commentaires.
Quoique les expressions régulières de Perl soient plus puissantes que les
expressions régulières ``mathématiques'', car elles présentent des
avantages comme les motifs mémorisés (\1 et ses potes), elles ne sont pas encore assez puissantes. Vous avez encore
besoin d'utiliser des techniques autres pour analyser du texte bien
équilibré, tel que du texte enclos par des parenthèses ou des accolades.
Une sous routine élaborée (pour du 7-bit ASCII seulement) pour extraire de
simples caractères se correspondant et peut-être imbriqués, comme ` et
', { et }, ou ( et ) peut être trouvée à http://www.perl.com/CPAN/authors/id/TOMC/scripts/pull_quotes.gz
.
Le module C::Scan module du CPAN contient de telles sous routines pour des usages internes, mais elles ne sont pas documentées.
La plupart des gens pensent que les expressions régulières gourmandes
reconnaissent autant qu'elles peuvent. Techniquement parlant, c'est en
réalité les quantificateurs (?, *, +, {}) qui sont gourmands plutôt que le motif entier; Perl préfère les
gourmandises locales et les gratifications immédiates à une gloutonnerie
globale. Pour avoir les versions non gourmandes des mêmes quantificateurs,
utilisez (??, *?,
+?, {}?).
Un exemple:
$s1 = $s2 = "J'ai très très froid";
$s1 =~ s/tr.*s //; # J'ai froid
$s2 =~ s/tr.*?s //; # J'ai très froid
Notez que la seconde substitution arrête la reconnaissance dès qu'un ``s ''
est rencontré. Le quantificateur *? dit effectivement au moteur des expressions régulières de trouver une
reconnaissance aussi vite que possible et de passer le contrôle à la suite,
comme de se refiler une patate chaude.
Utilisez la fonction split:
while (<>) {
foreach $word ( split ) {
# faire quelque chose avec $word ici
}
}
Notez que ce ne sont pas vraiment des mots dans le sens français; ce sont juste des suites de caractères différents de l'espace.
Pour travailler avec seulement des séquences alphanumériques, vous pourriez envisager
while (<>) {
foreach $word (m/(\w+)/g) {
# faire quelque chose avec $word ici
}
}
Pour faire cela, vous avez à sortir chaque mot du flux d'entrée. Nous appelerons mot une suite de caractères alphabétiques, de tirets et d'apostrophes plutôt qu'une suite de tout caractère sauf espace comme vue dans la question précédente:
while (<>) {
while ( /(\b[^\W_\d][\w'-]+\b)/g ) { # on rate "`mouton'"
$seen{$1}++;
}
}
while ( ($word, $count) = each %seen ) {
print "$count $word\n";
}
Si vous voulez faire la même chose avec les lignes, vous n'avez pas besoin d'une expression régulière:
while (<>) {
$seen{$_}++;
}
while ( ($line, $count) = each %seen ) {
print "$count $line";
}
Si vous voulez que le résultat soit trié, regardez la section sur Hashes.
Regardez le module String::Approx du CPAN.
Le code suivant est super-inefficace:
while (<FH>) {
foreach $pat (@patterns) {
if ( /$pat/ ) {
# faire quelque chose
}
}
}
A la place, vous avez besoin d'utiliser l'un des modules expérimentals d'extension d'expression régulière du CPAN (qui devra être plus que transformée pour remplir vos besoins), ou mettre quelque chose de ce genre, inspiré d'une routine du livre de Jeffrey Friedl:
sub _bm_build {
my $condition = shift;
my @regexp = @_; # cela ne DOIT pas être local(); besoin de my()
my $expr = join $condition => map { "m/\$regexp[$_]/o" } (0..$#regexp);
my $match_func = eval "sub { $expr }";
die if $@; # propage $@; cela ne devrait pas arriver!
return $match_func;
}
sub bm_and { _bm_build('&&', @_) }
sub bm_or { _bm_build('||', @_) }
$f1 = bm_and qw{
xterm
(?i)window
};
$f2 = bm_or qw{
\b[Ff]ree\b
\bBSD\B
(?i)sys(tem)?\s*[V5]\b
};
# remplis pour moi /etc/termcap, probablement
while ( <> ) {
print "1: $_" if &$f1;
print "2: $_" if &$f2;
}
Deux idées fausses communes sont que \b est synonyme de \s+, et que c'est la frontière entre les espaces et les autres caractères.
Aucune n'est correcte. \b est l'endroit entre un caractère \w et un \W
(c'est cela, \b est la frontière d'un ``mot''). C'est une assertion de longueur nulle comme ^, $, et tous les autres anchors, d'où il ne consomme aucun caractère. la page de manuel perlre décrit le comportement de tous ces metacaractères.
Voici des exemples d'utilisation incorrecte de \b, avec les corrections:
"deux mots" =~ /(\w+)\b(\w+)/; # MAUVAIS "deux mots" =~ /(\w+)\s+(\w+)/; # bon
" =matchless= text" =~ /\b=(\w+)=\b/; # MAUVAIS " =matchless= text" =~ /=(\w+)=/; # bon
Quoiqu'ils peuvent ne pas faire ce que vous pensez qu'ils font, \b et
\B peuvent être bien utiles. Pour un exemple d'utilisation correcte de
\b, regardez l'exemple de reconnaissance de mots dupliqués sur plusieurs
lignes.
Un exemple d'utilisation de \B est le motif \Best\B. Il trouvera les occurences de ``est'' seulement à l'intérieur des mots,
comme ``geste'', mais pas ``test'' ou ``estime''.
Parce qu'une fois que Perl voit que vous avez besoin d'une de ces variables quelque part, il doit les fournir pour chaque reconnaissance de motif. Le même mécanisme qui les prend en compte est fourni pour l'utilisation de $1, $2, etc... donc vous payez le même prix pour chaque expression régulière qui contient des parenthèses de capture. Mais si vous n'utilisez jamais $&, etc... dans votre script, alors les expressions régulières sans capture de parenthèses ne sont pas pénalisées. Donc, évitez $&, $' et $` si vous pouvez, mais si vous ne pouvez pas (et c'est très apprécié pour plusieurs algorithmes), une fois que vous les avez uilisées, utilisez-les comme vous voulez, car vous avez déjà payé le prix.
\G dans une expression régulière ?
La notation \G est utilisée dans une reconnaisssance ou substitution en conjonction avec
le modificateur /g (et ignoré s'il n'y a pas de /g) pour ancrer l'expression régulière à l'endroit où la dernière
reconnaissance a eu lieu, cad l'endroit indiqué par pos().
Par exemple, supposez que vous ayez une ligne de texte quotée dans un
courrier standard avec la notation Usenet (cad, commençant par des
>), et que vous vouliez changer chaque caractère de début >
en :. Vous pouvez faire ainsi:
s/^(>+)/':' x length($1)/gem;
Ou, en utilisant \G, le plus simple (et rapide):
s/\G>/:/g;
Une utilisation plus sophistiquée peut entraîner un analyseur
lexicographique. L'exemple suivant, à la lex, est de Jeffrey Friedl. Il ne
marche pas avec la 5.003 à cause d'un bug dans cette version, mais est ok à
partir de la 5.004. (Notez l'utilisation de /c, qui, lorsqu'une reconnaissance avec /g échoue, empêche de remettre la position de recherche au début de la
chaîne).
while (<>) {
chomp;
PARSER: {
m/ \G( \d+\b )/gcx && do { print "nombre: $1\n"; redo; };
m/ \G( \w+ )/gcx && do { print "mot: $1\n"; redo; };
m/ \G( \s+ )/gcx && do { print "espace: $1\n"; redo; };
m/ \G( [^\w\d]+ )/gcx && do { print "autre: $1\n"; redo; };
}
}
Bien sûr, cela pourrait avoir été écrit ainsi
while (<>) {
chomp;
PARSER: {
if ( /\G( \d+\b )/gcx {
print "nombre: $1\n";
redo PARSER;
}
if ( /\G( \w+ )/gcx {
print "mot: $1\n";
redo PARSER;
}
if ( /\G( \s+ )/gcx {
print "espace: $1\n";
redo PARSER;
}
if ( /\G( [^\w\d]+ )/gcx {
print "autre: $1\n";
redo PARSER;
}
}
}
Mais vous perdez l'alignement vertical des expressions régulières.
Bien qu'il soit vrai que les expressions régulières de Perl ressemblent aux
AFD (automate fini déterminé) du programme egrep(1), elles
sont dans les faits implémentées comme AFN (automate fini indéterminé) pour
permettre le retour en arrière et la mémorisation de sous-motif. Et elles
ne sont pas non plus conformes à POSIX, car celui-ci garantit le
comportement du pire dans tous les cas. (Il semble que certains préfèrent
une garantie de consistence, même quand ce qui est garanti est la lenteur).
Lisez le livre ``Mastering Regular Expressions'' (from O'Reilly) par
Jeffrey Friedl pour tous les détails que vous pouvez espérer connaître sur
ces matières (la référence complète est dans la page de manuel perlfaq2).
Grep et map construisent tous les deux une liste qu'ils retournent, sans tenir compte du contexte. Cela signifie que vous mettez Perl dans le trouble de construire une liste que vous allez ensuite ignorer. Ce n'est pas une façon de traiter un langage de programmation, espèce de petit bandit !
C'est dur et il n'y a pas de bonne manière. Perl ne supporte pas directement les grands caractères. Il prétend qu'un octet et un caractère sont synonymes. L'ensemble d'approches suivantes est offert par Jeffrey Friedl, dont l'article dans le numéro 5 du Perl Journal parle de cela de manière détaillée.
Supposons que vous ayiez un codage de ces mystérieux Martiens où les paires de lettre majuscules ASCII codent une lettre simple martienne (i.e. les 2 octets ``CV'' donne une simple lettre martienne, ainsi que ``SG'', ``VS'', ``XX'', etc.). D'autres octets représentent de simples caractères comme l'ASCII.
Ainsi, la chaîne martienne ``Je suis CVSGXX!'' utilise 15 octets pour coder les 12 caractères 'J', 'e', ' ', 's', 'u', 'i', 's', ' ', 'CV', 'SG', 'XX', '!'.
Maintenant, vous voulez chercher le simple caractère /GX/. Perl ne connaît rien au martien, donc il trouvera les 2 octets ``GX''
dans la chaîne ``Je suis CVSGXX!'', alors que ce carctère n'y est pas: il
semble y être car ``SG'' est à côté de ``XX'', mais il n'y a pas de réel
``GX''. C'est un grand problème.
Voici quelques manières, toutes pénibles, de traiter cela:
$martian =~ s/([A-Z][A-Z])/ $1 /g; # assurer que les octets ``martiens''
# ne soient plus contigüs
print "GX trouvé!\n" if $martian =~ /GX/;
Ou ainsi:
@chars = $martian =~ m/([A-Z][A-Z]|[^A-Z])/g;
# c'est conceptuellemnt similaire à: @chars = $text =~ m/(.)/g;
#
foreach $char (@chars) {
print "GX trouvé!\n", last if $char eq 'GX';
}
Ou encore ainsi:
while ($martian =~ m/\G([A-Z][A-Z]|.)/gs) { # \G probablement inutile
print "GX trouvé!\n", last if $1 eq 'GX';
}
Ou encore ainsi:
die "désolé, Perl ne supporte pas (encore) le Martien )-:\n";
En supplément, un simple programme qui convertit des demi-largeurs en pleine largeur katakana (dans le codage Shift-JIS ou EUC) est disponible au CPAN comme
Il y a plusieurs double- (et multi-) octets codages courament utilisés en ce moment. Plusieurs versions de ceux-ci ont des caractères de 1-, 2-, 3- et 4-octets, tous mélangés.
Copyright (c) 1997, 1998 Tom Christiansen et Nathan Torkington. Tous droits réservés.
Lorsque ce travail est inclus comme un élément de la distribution standard de Perl, ou comme une partie de sa documentation complète sous forme imprimée ou autrement, il ne peut être distribué que dans les limites fixées par la Perl's Artistic License. Toute distribution de ce fichier ou de ses dérivés hors de cet ensemble nécessite un accord particulier avec le titulaire des droits.
Indépendemment de sa distribution, tous les exemples de code de ce fichier sont ici placés dans le domaine public. Vous êtes autorisés et encouragés à utiliser ce code dans vos programmes que ce soit pour votre plaisir ou pour un profit. Un simple commentaire dans le code précisant l'origine serait de bonne courtoisie mais n'est pas indispensable.
Marianne Roger (maroger@maretmanu.org) La traduction française est distribuée avec les même droits que sa version originale (voir ci-dessus).
Régis Julié (Regis.Julie@cetelem.fr)