- En Python, avec
re,$peut correspondre non seulement à la fin de chaîne, mais aussi juste avant le dernier saut de ligne final, même lorsque le mode multiligne est désactivé - Parce que
^ressemble à « début de chaîne », il ne faut pas supposer que$se comporte de manière parfaitement symétrique ; sa signification réelle varie selon l’implémentation de l’expression régulière - Pour
"cat\n", les résultats de$,\zet\Zdiffèrent entre PHP, ECMAScript, Python, Go, Java 8, .NET 7.0 et Rust, et\za été ajouté en Python seulement dans Python 3.14 - Si l’on autorise le saut de ligne final, alors
$en mode multiligne correspond à"cat\n"sur toutes les plateformes du tableau ; en revanche, pour ne viser que la vraie fin hors saut de ligne, le choix de la syntaxe change - Si l’on ne veut pas faire correspondre le dernier saut de ligne, il faut utiliser
\zsur la plupart des plateformes, et envisager d’autres alternatives pour Python avant la 3.14 et pour ECMAScript
Où $ correspond dans re de Python
- Dans le module d’expressions régulières
rede Python,$peut correspondre à la fin de chaîne ou juste avant le dernier saut de ligne final, même lorsque le mode multiligne est désactivé cat$correspond bien à"lolcat"et pas à"internet cat video", ce qui semble simple, mais avec une fin de chaîne comme"cat\n", le résultat peut être différent de ce que l’on attend- Quand
re.MULTILINEest activé,$correspond à la fin de la chaîne ainsi qu’à la fin de chaque ligne, c’est-à-dire juste avant chaque saut de ligne - Même avec le comportement par défaut,
$correspond à la fin de la chaîne et, s’il y a un saut de ligne final, juste avant ce saut de ligne
Correspondre à la fin en excluant le dernier saut de ligne
- Pour viser strictement la fin de la chaîne,
$seul peut ne pas suffire, et\zcomme\Zsont des candidats possibles comme ancres de fin - D’après la documentation des expressions régulières Python et cette explication d’autres syntaxes regex, la prise en charge et la signification de
\zet\Zvarient selon l’implémentation - Pour
"cat\n", les différences sont les suivantes- PHP :
"cat$"correspond avec ou sans mode multiligne,"cat\z"ne correspond pas, et"cat\Z"correspond - ECMAScript :
"cat$"en mode multiligne correspond,"cat$"hors mode multiligne ne correspond pas, et\zcomme\Zne sont pas pris en charge - Python :
"cat$"correspond avec ou sans mode multiligne, et"cat\z"comme"cat\Z"ne correspondent pas à"cat\n" - Go et Rust :
"cat$"en mode multiligne correspond,"cat$"hors mode multiligne et"cat\z"ne correspondent pas, et\Zn’est pas pris en charge - Java 8 et .NET 7.0 :
"cat$"correspond avec ou sans mode multiligne,"cat\z"ne correspond pas, et"cat\Z"correspond
- PHP :
\zen Python a été ajouté dans Python 3.14 ; il n’était pas pris en charge dans les versions précédentes- Si l’on autorise le saut de ligne final, alors
$en mode multiligne correspond de façon cohérente à"cat\n"sur toutes les plateformes du tableau - Si l’on ne veut pas faire correspondre le saut de ligne final, il faut utiliser
\zsur la plupart des plateformes ; avant Python 3.14, il faut utiliser\Z, et en ECMAScript,$sans mode multiligne - Les données du tableau ont été collectées sur regex101.com et n’ont pas été vérifiées dans de vrais environnements d’exécution
1 commentaires
Avis de Hacker News
Depuis longtemps, je considère
^comme le « début de ligne » et$comme la « fin de ligne ».Quand on manipule des expressions régulières, on traite souvent le texte ligne par ligne, donc le résultat est souvent le même, mais la façon dont je me représente ces opérateurs reste plus proche de la « ligne » que de la « chaîne ».
C’est sans doute parce que j’ai découvert les expressions régulières avec
grep, ce qui m’a donné l’habitude de voir l’entrée comme des lignes plutôt que comme une chaîne.J’utilise les expressions régulières depuis près de 20 ans, mais je crois que c’est la première fois que j’entends dire que
$signifie la fin de la chaîne ; je l’ai toujours vu comme la fin de ligne.^comme le « début de la chaîne ».En réalité, tout comme
$est la « fin de ligne »,^est aussi le « début de ligne », et le début de la chaîne ressemble plutôt à\A, la fin de la chaîne à\Z.$se comporte par défaut comme une assertion de lookahead positive sur la fin de la chaîne.Il ne matche ni ne consomme le caractère de saut de ligne.
En mode multiligne seulement, il matche les positions de saut de ligne, mais même là, il semble ne pas les consommer.
En pratique, en utilisant
$, je n’ai pas pu créer une regexp qui capture le dernier caractère d’une ligne, consomme le saut de ligne, puis capture le premier caractère de la ligne suivante ; le groupe de capture s’arrête simplement à$.grep, qui m’a inculqué cette perception.Les expressions régulières POSIX et celles de Python sont différentes.
En général, la syntaxe des expressions régulières n’est pas universelle : il faut donc consulter la documentation de l’implémentation utilisée.
Selon le chapitre 9 de POSIX, les expressions régulières opèrent sur des chaînes, mais certains utilitaires limitent le traitement ligne par ligne.
Il y est aussi indiqué que
$est une ancre fixée à la fin de la chaîne à faire matcher ; au final, c’est donc l’utilitaire ou le mode qui détermine si$signifie la fin de chaîne ou la fin de ligne.Les outils courants comme
grep,sed,awkou Python fonctionnent par défaut ligne par ligne, et le traitent donc généralement comme une fin de ligne.Il n’existe pas une seule syntaxe universelle d’expressions régulières.
Sans savoir quel langage et quelles options sont utilisés, on ne peut pas lire ni écrire une expression régulière de manière fiable.
https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1...
Ce sujet est une très bonne occasion de faire découvrir Robert Elder à ceux qui ne le connaissent pas.
Il produit du bon contenu sur YouTube et sur son blog, et dans sa série sur les expressions régulières, il creuse assez profondément les différences de comportement entre les implémentations des divers outils.
Sa vidéo récente est bonne aussi : https://www.youtube.com/watch?v=ys7yUyyQA-Y
Il a aussi beaucoup de contenus susceptibles d’intéresser les lecteurs de HN, et aborde des sujets comme la réalité et les difficultés du consulting.
https://www.youtube.com/@RobertElderSoftware
https://blog.robertelder.org/
https://blog.robertelder.org/regular-expressions/
https://www.youtube.com/watch?v=cK87ktENPrI
Quand j’ai appris Perl, les expressions régulières ont été l’une des premières choses que j’ai vraiment intégrées, et aujourd’hui encore, grâce au livre « Camel », Perl garde une place confortable dans un coin de ma tête.
Le savoir le plus important aujourd’hui, c’est que ça dépend de l’implémentation ; j’ai donc pris l’habitude de sortir la fiche de référence correspondante à chaque fois que je travaille sur quelque chose.
Par exemple, les expressions régulières d’Emacs ne prennent pas en charge les caractères de mot sous la forme
\wet il faut utiliser des classes de caractères du genre\s_-, ce qui est agaçant, mais je trouve qu’Emacs est excellent pour la documentation et la découvrabilité.Certains utilitaires exigent d’échapper les parenthèses, d’autres non ; parfois ce comportement est configurable, parfois non.
Après être passé par toutes les phases de confusion, d’agacement et de déni, je l’accepte simplement.
Les concepts sont les mêmes partout, mais les dialectes changent.
Dans le shell en particulier, plutôt que de me demander si
sed/grep/awksont GNU ou BSD, j’insère bien plus souventperldans le pipeline.Perl donne l’impression qu’un chat a marché sur le clavier.
J’ai l’impression d’entendre d’innombrables mauvais responsables du recrutement ajouter « comment matchez-vous la fin d’une chaîne dans une expression régulière ? » à leur liste de questions pièges.
C’est étrange d’omettre Perl d’une liste sur les expressions régulières.
Dans la documentation
perlre,$est décrit ainsi : il matche la fin de la chaîne, ou l’emplacement avant le saut de ligne final de la chaîne, ou, avec/m, avant n’importe quel saut de ligne.Cela montre peut-être aussi à quel point Perl est aujourd’hui sorti du champ d’attention.
Raku, anciennement Perl 6, a défini
^et$comme le début/la fin de chaîne, et introduit^^et$$pour le début/la fin de ligneIl n’y a pas de mode multiligne, et il n’est pas nécessaire
\hreprésente aussi les espaces horizontaux,\vles espaces verticauxC’est l’avantage d’avoir tout repensé et réécrit : on a pu apprendre du fait que l’ancien comportement surprenait les gens
J’ai l’impression qu’on a mélangé au hasard une syntaxe façon bruit de ligne apprise pendant des décennies
L’inverse aurait été plus clair comme valeur par défaut
Il aurait semblé plus naturel d’utiliser
^et$pour les lignes, et^^et$$pour les chaînesParce que cela ressemble à
^^line1$\n^line2$\n^line3$\n$En plus, Perl 6 n’est pas partout, alors que Perl 5 est partout
^^paraît plus « début » que^Comme on traite généralement des lignes en les passant à une regex, le choix d’utiliser un seul
^et$pour toute la chaîne préserve dans une certaine mesure la rétrocompatibilitéJe me demande si quelqu’un considère vraiment que les expressions régulières sont standardisées
À chaque passage à un nouvel environnement, j’ai toujours dû les réapprendre
Il existe sûrement d’autres dialectes de regex, mais je ne les rencontre pas, et ce que je connais suffit dans la plupart des cas
C’est un peu comme conduire une voiture de location
Elle se comporte un peu différemment de ma voiture, avec des fonctions manquantes et d’autres en plus, mais dans l’ensemble elles se ressemblent presque toutes
Donc, au moins pour moi, les regex sont standardisées par plusieurs standards officiels publiés
https://open-std.org/jtc1/sc22/…
https://pubs.opengroup.org/onlinepubs/9699919799/…
https://262.ecma-international.org/14.0/…
Beaucoup de systèmes, y compris JavaScript, ont implémenté PCRE, parce que Perl ajoutait beaucoup d’extensions utiles au système POSIX
Si je me souviens bien, RE2 cherchait à limiter les problèmes de performances et les comportements étranges des systèmes existants, et je pensais qu’il avait été entièrement implémenté en Go
J’ai découvert plus tard que RE2 était antérieur à Go
Malgré tout, le sens de
$et la manière de passer en mode multiligne sont en général assez cohérentsLes gens confondent chaînes et lignes
Une chaîne est une séquence de caractères, tandis qu’une ligne peut être vue de deux façons
Si l’on considère le saut de ligne comme un terminateur de ligne, une ligne est constituée de zéro ou plusieurs caractères autres qu’un saut de ligne, suivis d’un saut de ligne ; sans saut de ligne final, ce n’est pas une ligne complète
C’est le point de vue adopté par POSIX
Si l’on considère le saut de ligne comme un séparateur de lignes, une ligne est une séquence de zéro ou plusieurs caractères autres qu’un saut de ligne
Dans les deux cas, le contenu de la ligne se termine avant le saut de ligne
La sémantique de
^et$est orientée ligne, que l’on soit en mode monoligne ou multilignePour une sémantique orientée chaîne — ou, lorsqu’on manipule des fichiers, parfois orientée fichier entier — il faut utiliser
\Aet\Z, ou leurs équivalentsLes deux interprétations ont leurs avantages
Quand on transmet du texte sur une liaison série, utiliser le saut de ligne comme terminateur permet de savoir facilement si l’on a reçu une ligne complète
Dans un fichier texte, voir le saut de ligne comme un séparateur peut être plus pratique, car la dernière ligne ne se retrouve pas dans un état invalide ; mais avec un terminateur de ligne, on peut détecter une ligne écrite de manière incomplète
Cela a provoqué plusieurs bugs graves dans des applications basées sur Ruby
Il faut toujours utiliser
\A\zhttps://homakov.blogspot.com/2012/05/saferweb-injects-in-var...
https://sakurity.com/blog/2015/02/28/openuri.html
https://sakurity.com/blog/2015/06/04/mongo_ruby_regexp.html