web-dev-qa-db-fra.com

Comment puis-je faire correspondre une chaîne délimitée par des guillemets avec une expression régulière?

Si j'essaie de faire correspondre une chaîne délimitée par des guillemets avec une expression régulière, laquelle des options suivantes est "meilleure" (où "mieux" signifie à la fois plus efficace et moins susceptible de faire quelque chose d'inattendu):

/"[^"]+"/ # match quote, then everything that's not a quote, then a quote

ou

/".+?"/   # match quote, then *anything* (non-greedy), then a quote

Supposons pour cette question que les chaînes vides (c'est-à-dire "") ne soient pas un problème. Il me semble (pas de débutant regex, mais certainement pas d'expert) que ceux-ci seront équivalents.

pdate: Après réflexion, je pense que changer le + caractères à * gèrera quand même correctement les chaînes vides.

30
Graeme Perrow

Vous devez utiliser le numéro un, car le numéro deux est une mauvaise pratique. Considérez que le développeur qui vient après vous souhaite faire correspondre les chaînes suivies d'un point d'exclamation. Doit-il utiliser:

"[^"]*"!

ou:

".*?"!

La différence apparaît lorsque vous avez le sujet:

"one" "two"!

Le premier regex correspond:

"two"!

tandis que le deuxième regex correspond:

"one" "two"!

Soyez toujours aussi précis que possible. Utilisez la classe de personnage nié lorsque vous le pouvez.

Une autre différence est que [^ "] * peut s'étendre sur plusieurs lignes, contrairement à. * Sauf si vous utilisez le mode de ligne unique. [^"\N] * exclut également les sauts de ligne.

En ce qui concerne le retour arrière, le deuxième regex revient en arrière pour chaque caractère de chaque chaîne qu'il correspond. Si le devis de clôture est manquant, les deux expressions régulières reviendront en arrière dans tout le fichier. Seul l'ordre dans lequel la marche arrière est différente. Ainsi, en théorie, le premier regex est plus rapide. En pratique, vous ne remarquerez pas la différence.

38
Jan Goyvaerts

Plus compliqué, mais il gère les guillemets échappés et les antislashs échappés (les antislashs échappés suivis d'un devis ne sont pas un problème)

/(["'])((\\{2})*|(.*?[^\\](\\{2})*))\1/

Exemples:
"bonjour \" monde " correspond à " bonjour\"monde"
"bonjour \\" monde " correspond à " bonjour \\ "

14
nico

Je voudrais suggerer:

([\"'])(?:\\\1|.)*?\1

Mais seulement parce qu'il gère les caractères de guillemets échappés et permet à la fois à 'et "d'être le caractère de guillemet. Je suggère également de lire cet article qui aborde ce problème en profondeur:

http://blog.stevenlevithan.com/archives/match-quoted-string

Cependant, à moins que vous n'ayez un problème de performances sérieux ou que vous ne soyez pas sûr des guillemets intégrés, optez pour le plus simple et le plus lisible:

/".*?"/

Je dois admettre que les motifs non gourmands ne sont pas l'expression régulière de base du style Unix, mais ils deviennent assez courants. Je n'ai toujours pas l'habitude de grouper des opérateurs comme (?: Stuff).

10
Harold Bamford

Je dirais que le second est meilleur, car il échoue plus rapidement lorsque la terminaison " est manquant. Le premier reviendra sur la chaîne, une opération potentiellement coûteuse. Une expression rationnelle alternative si vous utilisez Perl 5.10 serait /"[^"]++"/. Il transmet la même signification que la version 1, mais est aussi rapide que la version deux.

5
Leon Timmermans

J'irais pour le numéro deux car il est beaucoup plus facile à lire. Mais je voudrais toujours faire correspondre des chaînes vides, donc j'utiliserais:

/".*?"/
4
PEZ

Du point de vue des performances (boucle extrêmement lourde et longue sur de longues cordes), je pourrais imaginer que

"[^"]*"

est plus rapide que

".*?"

parce que ce dernier ferait une vérification supplémentaire pour chaque étape: jeter un œil au personnage suivant. Le premier serait capable de rouler sans réfléchir sur la corde.

Comme je l'ai dit, dans des scénarios réels, cela ne serait guère perceptible. Par conséquent, j'irais avec le numéro deux (si ma saveur regex actuelle le supporte, c'est-à-dire) car il est beaucoup plus lisible. Sinon avec le numéro un, bien sûr.

2
Tomalak

L'utilisation de la classe de caractères à exclure empêche la correspondance lorsque le caractère de limite (guillemets doubles, dans votre exemple) est présent ailleurs dans l'entrée.

Votre exemple # 1:

/"[^"]+"/ # match quote, then everything that's not a quote, then a quote

ne correspond qu'à la plus petite paire de citations correspondantes - excellente, et la plupart du temps c'est tout ce dont vous aurez besoin. Cependant, si vous avez des citations imbriquées et que vous êtes intéressé par la plus grande paire de citations correspondantes (ou par toutes les citations correspondantes), vous êtes dans une situation beaucoup plus compliquée.

Heureusement, Damian Conway est prêt avec le sauvetage: Text :: Balanced est là pour vous, si vous trouvez qu'il y a plusieurs guillemets correspondants. Il a également l'avantage de correspondre à d'autres signes de ponctuation appariés, par ex. parenthèses.

1
Trochee

Je préfère le premier regex, mais c'est certainement une question de goût.

Le premier pourrait être plus efficace?

Search for double-quote
add double-quote to group
for each char:
    if double-quote:
        break
    add to group
add double-quote to group

Vs quelque chose d'un peu plus compliqué impliquant un back-tracking?

0
Douglas Leeder

Étant donné que je ne connaissais même pas le "*?" chose jusqu'à aujourd'hui, et j'utilise des expressions régulières depuis plus de 20 ans, je voterais en faveur de la première. Cela indique clairement ce que vous essayez de faire - vous essayez de faire correspondre une chaîne qui ne comprend pas de guillemets.

0
Paul Tomblin