RegEx: saisie de valeurs entre guillemets

240

J'ai une valeur comme celle-ci:

"Foo Bar" "Another Value" something else

Quelle expression régulière renverra les valeurs entre guillemets (par exemple Foo Baret Another Value)?

deadbug
la source
Lié à stackoverflow.com/questions/138552/…
Andrew Edgecombe

Réponses:

361

J'utilise les éléments suivants avec beaucoup de succès:

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

Il prend également en charge les citations imbriquées.

Pour ceux qui veulent une explication plus approfondie de la façon dont cela fonctionne, voici une explication de l' éphémient utilisateur :

([""'])correspondre à un devis; ((?=(\\?))\2.)si une barre oblique inverse existe, engloutissez-la et, que cela se produise ou non, faites correspondre un caractère; *?correspondre plusieurs fois (sans avidité, pour ne pas manger la citation de clôture); \1correspondre à la même citation qui a été utilisée pour l'ouverture.

Adam
la source
6
@ Steve: ce serait également correspondre, à tort, "foo\". L'astuce d'anticipation rend le ?quantificateur possessif (même si la saveur regex ne prend pas en charge la ?+syntaxe ou le groupement atomique)
Robin
1
Avec python, cela génère une erreur: sre_constants.error: ne peut pas faire référence au groupe ouvert
a1an
9
Cela renvoie les valeurs, y compris les guillemets correspondants. N'y a-t-il aucune chance de renvoyer uniquement le contenu entre les devis, comme cela a été demandé?
Martin Schneider
4
Abuser d'une tête de lecture en tant que quantificateur possessif est complètement inutile et déroutant. Il suffit d'utiliser une alternance:(["'])(?:\\.|[^\\])*?\1
Aran-Fey
2
comment éviter les chaînes vides?
Vikas Bansal du
333

En général, le fragment d'expression régulière suivant correspond à ce que vous recherchez:

"(.*?)"

Cela utilise le non gourmand *? opérateur pour tout capturer jusqu'à mais sans inclure la prochaine citation double. Ensuite, vous utilisez un mécanisme spécifique à la langue pour extraire le texte correspondant.

En Python, vous pouvez faire:

>>> import re
>>> string = '"Foo Bar" "Another Value"'
>>> print re.findall(r'"(.*?)"', string)
['Foo Bar', 'Another Value']
Greg Hewgill
la source
11
C'est bien, mais il ne gère pas les chaînes avec des guillemets échappés. par exemple,"hello \" world"
robbyt
En utilisant la correspondance de JavaScript, cela correspondra également aux guillemets. Cela fonctionnera avec une itération sur exec comme décrit ici: stackoverflow.com/questions/7998180/…
Kiechlus
4
@robbyt Je sais qu'il est un peu tard pour une réponse mais qu'en est-il d'un lookbehind négatif? "(.*?(?<!\\))"
Mateus
4
Merci - c'est plus simple si vous êtes sûr qu'il n'y a pas de citations échappées à traiter.
squarecandy
Un mot. Impressionnant !
Shiva Avula
89

J'irais pour:

"([^"]*)"

Le [^ "] est regex pour tout caractère sauf ' " '.
La raison pour laquelle j'utilise ceci sur l'opérateur many non gourmand, c'est que je dois continuer à chercher cela juste pour m'assurer que je le reçois correctement.

Martin York
la source
1
Cela se comporte également bien entre les différentes interprétations des expressions rationnelles.
Phil Bennett
5
Cela a sauvé ma raison. Dans l'implémentation RegEx de .NET, "(. *?)" N'a pas l'effet souhaité (il n'agit pas de manière non gourmande), mais "([^"] *) ".
Jens Neubauer
C'est la meilleure réponse imo. Merci
Lmao 123
28

Voyons deux façons efficaces de gérer les guillemets échappés. Ces motifs ne sont pas conçus pour être concis ni esthétiques, mais pour être efficaces.

Ces méthodes utilisent la première discrimination de caractère pour trouver rapidement des guillemets dans la chaîne sans le coût d'une alternance. (L'idée est de supprimer rapidement les caractères qui ne sont pas des guillemets sans tester les deux branches de l'alternance.)

Le contenu entre guillemets est décrit avec une boucle déroulée (au lieu d'une alternance répétée) pour être plus efficace aussi: [^"\\]*(?:\\.[^"\\]*)*

Évidemment, pour traiter les chaînes qui n'ont pas des guillemets équilibrés, vous pouvez utiliser des quantificateurs possessifs à la place: [^"\\]*+(?:\\.[^"\\]*)*+ou une solution de contournement pour les émuler, pour éviter trop de retour en arrière. Vous pouvez également choisir qu'une partie entre guillemets puisse être une citation d'ouverture jusqu'à la citation suivante (non échappée) ou la fin de la chaîne. Dans ce cas, il n'est pas nécessaire d'utiliser des quantificateurs possessifs, il vous suffit de rendre la dernière citation facultative.

Remarque: parfois, les guillemets ne sont pas échappés par une barre oblique inverse, mais en répétant la citation. Dans ce cas, le sous-modèle de contenu ressemble à ceci:[^"]*(?:""[^"]*)*

Les motifs évitent l'utilisation d'un groupe de capture et d'une référence arrière (je veux dire quelque chose comme (["']).....\1) et utilisent une alternance simple mais avec ["']au début, en facteur.

Perl comme:

["'](?:(?<=")[^"\\]*(?s:\\.[^"\\]*)*"|(?<=')[^'\\]*(?s:\\.[^'\\]*)*')

(notez qu'il (?s:...)s'agit d'un sucre syntaxique pour activer le mode dotall / singleline dans le groupe non capturant. Si cette syntaxe n'est pas prise en charge, vous pouvez facilement activer ce mode pour tout le modèle ou remplacer le point par [\s\S])

(La façon dont ce modèle est écrit est totalement "manuelle" et ne tient pas compte des éventuelles optimisations internes du moteur)

Script ECMA:

(?=["'])(?:"[^"\\]*(?:\\[\s\S][^"\\]*)*"|'[^'\\]*(?:\\[\s\S][^'\\]*)*')

POSIX étendu:

"[^"\\]*(\\(.|\n)[^"\\]*)*"|'[^'\\]*(\\(.|\n)[^'\\]*)*'

ou simplement:

"([^"\\]|\\.|\\\n)*"|'([^'\\]|\\.|\\\n)*'
Casimir et Hippolyte
la source
1
Python accepte le script ECMA avec un format de chaîne brut, c'est-à-dire r "" "Script ECMA" ""
a1an
1
C'est génial, il a été très facile d'adapter votre ECMA pour fonctionner avec les nouveaux retours de ligne et de chariot entre guillemets doubles.
Douglas Gaskell
@ douglasg14b: Merci. Notez que si vous souhaitez l'utiliser en Javascript, il vous suffit d'utiliser la notation littérale /pattern/sans rien échapper (au lieu de la notation objet new RegExp("(?=[\"'])(?:\"[^\"\\\\]*...");)
Casimir et Hippolyte
@ a1an: oui, mais vous pouvez utiliser la version Perl si vous supprimez le sici: (?s:et si vous mettez (?s)quelque part dans le motif.
Casimir et Hippolyte
16

Le RegEx de la réponse acceptée renvoie les valeurs, y compris leurs guillemets environnants: "Foo Bar"et "Another Value"sous forme de correspondances.

Voici RegEx qui ne renvoie que les valeurs entre guillemets (comme l'interrogateur le demandait):

Citations doubles uniquement (utilisez la valeur du groupe de capture n ° 1):

"(.*?[^\\])"

Citations simples uniquement (utilisez la valeur du groupe de capture n ° 1):

'(.*?[^\\])'

Les deux (utilisez la valeur du groupe de capture n ° 2):

(["'])(.*?[^\\])\1

-

Toutes les citations échappées et imbriquées de support.

Martin Schneider
la source
S'il vous plaît, pourquoi cela fonctionne-t-il? J'utilisais src="(.*)"mais évidemment il sélectionnait tout avant le dernier ", votre REGEX, cependant, ne sélectionnait que le contenu src =" ", mais je ne comprenais pas comment?
Lucas Bustamante
J'aime beaucoup celui-ci pour sa simplicité mais il ne gère pas très bien les valeurs vides ou sans valeur entre guillemets comme je l'ai découvert
RedactedProfile
16

Curieusement, aucune de ces réponses ne produit une expression régulière où la correspondance renvoyée est le texte à l'intérieur des guillemets, ce qui est demandé. MA-Madden essaie mais n'obtient que le match intérieur en tant que groupe capturé plutôt que le match entier. Une façon de le faire serait:

(?<=(["']\b))(?:(?=(\\?))\2.)*?(?=\1)

Des exemples de cela peuvent être vus dans cette démo https://regex101.com/r/Hbj8aP/1

La clé ici est le lookbehind positif au début (le ?<=) et le lookahead positif à la fin (le ?=). Le lookbehind regarde derrière le caractère actuel pour vérifier une citation, s'il est trouvé, commencez à partir de là, puis le lookahead vérifie le caractère à venir pour une citation et s'il est trouvé, arrêtez-vous sur ce caractère. Le groupe de recherche (le ["']) est placé entre crochets pour créer un groupe pour la citation trouvée au début, il est ensuite utilisé à la fin de l'anticipation (?=\1)pour s'assurer qu'il ne s'arrête que lorsqu'il trouve la citation correspondante.

La seule autre complication est que, parce que l'antichambre ne consomme pas réellement le guillemet final, il sera retrouvé par le regard de départ qui entraîne la correspondance du texte entre les guillemets de fin et de début sur la même ligne. Mettre une limite de mot sur la citation d'ouverture ( ["']\b) aide à cela, bien que j'aimerais idéalement passer devant l'antichambre, mais je ne pense pas que ce soit possible. Le bit permettant aux personnages échappés au milieu que j'ai pris directement de la réponse d'Adam.

IrishDubGuy
la source
11

Une réponse très tardive, mais j'aime répondre

(\"[\w\s]+\")

http://regex101.com/r/cB0kB8/1

Suganthan Madhavan Pillai
la source
Fonctionne bien en php.
Parapluie
La seule réponse à ce jour pour capturer les deux "HomePage" dans: localiser ["Page d'accueil"] localiser ["Page d'accueil"]
jBelanger
8

Le motif (["'])(?:(?=(\\?))\2.)*?\1ci-dessus fait l'affaire mais je suis préoccupé par ses performances (c'est pas mal mais ça pourrait être mieux). Le mien en dessous est ~ 20% plus rapide.

Le modèle "(.*?)"est juste incomplet. Mon conseil pour tous ceux qui lisent ceci est juste de ne pas l'utiliser !!!

Par exemple, il ne peut pas capturer de nombreuses chaînes (si nécessaire, je peux fournir un cas de test exhaustif) comme celui ci-dessous:

$ string = 'Comment ça va? Je vais \'bien, merci ';

Les autres sont tout aussi "bons" que celui ci-dessus.

Si vous vous souciez vraiment à la fois des performances et de la précision, commencez par celui ci-dessous:

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

Dans mes tests, il a couvert toutes les chaînes que j'ai rencontrées, mais si vous trouvez quelque chose qui ne fonctionne pas, je le mettrais à jour avec plaisir.

Vérifiez mon modèle dans un testeur de regex en ligne .

Eugen Mihailescu
la source
1
J'aime la simplicité de votre modèle, mais le modèle de Casimir et Hippolyte en termes de performances souffle toutes les solutions étendues hors de l'eau. En outre, il semble que votre modèle ait des problèmes avec les cas de bord étendus comme une citation échappée à la fin de la phrase.
wp78de
7

J'ai aimé la solution d'Eugen Mihailescu pour faire correspondre le contenu entre les citations tout en permettant d'échapper aux citations. Cependant, j'ai découvert quelques problèmes avec l'échappement et j'ai trouvé l'expression régulière suivante pour les résoudre:

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

Il fait l'affaire et est toujours assez simple et facile à entretenir.

Démo (avec quelques cas de test supplémentaires; n'hésitez pas à l'utiliser et à l'étendre).


PS: Si vous voulez simplement le contenu entre guillemets dans le match complet ( $0), et que vous n'avez pas peur de la pénalité de performance, utilisez:

(?<=(['"])\b)(?:(?!\1|\\).|\\.)*(?=\1)

Malheureusement, sans les guillemets comme ancres, j'ai dû ajouter une frontière \bqui ne fonctionne pas bien avec des espaces et des caractères de frontière non-mot après la citation de départ.

Vous pouvez également modifier la version initiale en ajoutant simplement un groupe et en extraire la forme de chaîne$2 :

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

PPS: Si vous vous concentrez uniquement sur l'efficacité, optez pour la solution de Casimir et Hippolyte ; c'est un bon.

wp78de
la source
observation: la deuxième expression régulière manque une valeur avec un signe moins -, comme dans les coordonnées de longitude.
Crowcoder
Je n'ai rien changé. Si vous n'observez pas le problème, c'est peut-être la saveur de l'expression régulière que j'utilise. J'utilisais le regex101site, je pense que le regex de style php.
Crowcoder
Voici la démo de ce dont je parle. Je m'attendais à ce qu'il corresponde à la longitude (-96.74025) mais ce n'est pas le cas.
Crowcoder
@Crowcoder Merci. Oui, cela est causé par la limite de mot qui agit comme une ancre et aide à éviter les correspondances qui se chevauchent, mais ne joue pas bien avec votre entrée. Un groupe supplémentaire est en fait la meilleure option, comme indiqué dans la réponse mise à jour.
wp78de
6

Cette version

  • comptes pour les citations échappées
  • contrôle le retour en arrière

    /(["'])((?:(?!\1)[^\\]|(?:\\\\)*\\[^\\])*)\1/
Axeman
la source
Cela s'étend sur plusieurs chaînes et ne semble pas gérer correctement une double barre oblique inverse, par exemple la chaîne: foo 'stri \\ ng 1' bar 'chaîne 2' et 'chaîne 3' Debuggex Demo
miracle2k
Vous ne pouvez pas utiliser de référence arrière dans une classe de caractères.
HamZa
5

PLUS DE RÉPONSES! Voici la solution que j'ai utilisée

\"([^\"]*?icon[^\"]*?)\"

TLDR;
remplacez l' icône du mot par ce que vous recherchez dans lesdites citations et le tour est joué!


La façon dont cela fonctionne est qu'il recherche le mot-clé et ne se soucie pas de quoi d'autre entre les guillemets. EG:
id="fb-icon"
id="icon-close"
id="large-icon-close"
le regex cherche un guillemet "
puis il cherche tout groupe de lettres possible qui ne l'est pas "
jusqu'à ce qu'il trouve icon
et tout groupe de lettres possible qui ne l'est pas "
alors il cherche une fermeture"

James Harrington
la source
1
Merci beaucoup. a été en mesure de remplacer chaque occurrence de name="value"par name={"value"}puisque l'expression régulière de cette réponse renvoie icon/ valuecomme deuxième groupe (contrairement à la réponse acceptée). Trouver : =\"([^\"]*?[^\"]*?)\" Remplacer :={"$1"}
Palisand
Voulez-vous expliquer le downvote? cela fonctionne bien dans certaines situations.
James Harrington
Tu me réponds?
Palisand
@Palisand personne n'a voté contre ce post l'autre jour sans explication.
James Harrington
cela semble être la seule réponse qui trouve un texte spécifique à l'intérieur des guillemets
Top-Master
4

J'ai aimé la version plus expansive d'Axeman, mais j'ai eu quelques problèmes avec elle (elle ne correspondait pas par exemple

foo "string \\ string" bar

ou

foo "string1"   bar   "string2"

correctement, j'ai donc essayé de le réparer:

# opening quote
(["'])
   (
     # repeat (non-greedy, so we don't span multiple strings)
     (?:
       # anything, except not the opening quote, and not 
       # a backslash, which are handled separately.
       (?!\1)[^\\]
       |
       # consume any double backslash (unnecessary?)
       (?:\\\\)*       
       |
       # Allow backslash to escape characters
       \\.
     )*?
   )
# same character as opening quote
\1
miracle2k
la source
3
string = "\" foo bar\" \"loloo\""
print re.findall(r'"(.*?)"',string)

essayez-le, fonctionne comme un charme !!!

\ indique un caractère de saut

mobman
la source
Si cette première ligne est le code Python réel, cela va créer la chaîne " foo bar" "loloo". Je pense que vous vouliez dire pour envelopper que dans une chaîne brute comme vous avez fait avec la regex: r'"\" foo bar\" \"loloo\""'. Veuillez utiliser les excellentes capacités de formatage de SO chaque fois que cela est approprié. Ce n'est pas seulement des cosmétiques; nous ne pouvons littéralement pas dire ce que vous essayez de dire si vous ne les utilisez pas. Et bienvenue dans Stack Overflow !
Alan Moore
merci pour les conseils alan, je suis en fait nouveau dans cette communauté, la prochaine fois je garderai sûrement tout cela à l'esprit ... sincères excuses.
mobman
2

Contrairement à la réponse d'Adam, j'en ai une simple mais efficace:

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

Et ajoutez simplement des parenthèses si vous souhaitez obtenir du contenu entre guillemets comme ceci:

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

Correspond ensuite au caractère de $1citation et à $2la chaîne de contenu.

lon
la source
1
echo 'junk "Foo Bar" not empty one "" this "but this" and this neither' | sed 's/[^\"]*\"\([^\"]*\)\"[^\"]*/>\1</g'

Cela se traduira par:> Foo Bar <> <> mais ce <

Ici, j'ai montré la chaîne de résultat entre> <pour plus de clarté, en utilisant également la version non gourmande avec cette commande sed, nous jetons d'abord les fichiers indésirables avant et après ces "", puis nous les remplaçons par la partie entre les "" et entourez ceci de> <.

amo-ej1
la source
1

De Greg H., j'ai pu créer cette expression régulière pour répondre à mes besoins.

J'avais besoin de faire correspondre une valeur spécifique qualifiée en étant entre guillemets. Il doit s'agir d'une correspondance complète, aucune correspondance partielle ne devrait déclencher un hit

Par exemple, "test" ne peut pas correspondre à "test2".

reg = r"""(['"])(%s)\1"""
if re.search(reg%(needle), haystack, re.IGNORECASE):
    print "winning..."

chasseur

motoprog
la source
1

Si vous essayez de trouver des chaînes qui n'ont qu'un certain suffixe, comme la syntaxe à points, vous pouvez essayer ceci:

\"([^\"]*?[^\"]*?)\".localized

.localizedest le suffixe.

Exemple:

print("this is something I need to return".localized + "so is this".localized + "but this is not")

Il capturera "this is something I need to return".localizedet "so is this".localizednon "but this is not".

OffensivementBad
la source
1

Une réponse supplémentaire pour le sous-ensemble de codeurs Microsoft VBA, un seul utilise la bibliothèque Microsoft VBScript Regular Expressions 5.5et cela donne le code suivant

Sub TestRegularExpression()

    Dim oRE As VBScript_RegExp_55.RegExp    '* Tools->References: Microsoft VBScript Regular Expressions 5.5
    Set oRE = New VBScript_RegExp_55.RegExp

    oRE.Pattern = """([^""]*)"""


    oRE.Global = True

    Dim sTest As String
    sTest = """Foo Bar"" ""Another Value"" something else"

    Debug.Assert oRE.test(sTest)

    Dim oMatchCol As VBScript_RegExp_55.MatchCollection
    Set oMatchCol = oRE.Execute(sTest)
    Debug.Assert oMatchCol.Count = 2

    Dim oMatch As Match
    For Each oMatch In oMatchCol
        Debug.Print oMatch.SubMatches(0)

    Next oMatch

End Sub
S Meaden
la source
0

Pour moi a travaillé celui-ci:

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

J'ai utilisé dans une phrase comme celle-ci:

preg_match_all('|([\'"])(.*?)\1|i', $cont, $matches);

et cela a très bien fonctionné.

Alexandru Furculita
la source
Une faiblesse de cette approche est qu'elle correspondra lorsqu'une chaîne commence par un guillemet simple et se termine par un guillemet double, ou vice versa.
Ghopper21
Il a également des problèmes pour attraper "N'oubliez pas le @" - Il s'arrête après "Don".
Benny Neugebauer
0

Toutes les réponses ci-dessus sont bonnes .... sauf qu'elles ne prennent pas en charge tous les caractères unicode! à ECMA Script (Javascript)

Si vous êtes un utilisateur de nœud, vous souhaiterez peut-être la version modifiée de la réponse acceptée qui prend en charge tous les caractères unicode:

/(?<=((?<=[\s,.:;"']|^)["']))(?:(?=(\\?))\2.)*?(?=\1)/gmu

Essayez ici .

Donovan P
la source
1
Qu'est-ce qu'un caractère non unicode? AFAIK unicode couvre tous les caractères.
Toto
1
Pourquoi pensez-vous que c'est une question javascript? De plus, lookbehind n'est pas pris en charge dans tous les navigateurs, regex101 jette? The preceding token is not quantifiable
Toto
@Toto, ce que je veux dire, c'est "ne prend pas en charge tous les caractères unicode". Je vous remercie. Bien que la question porte sur l'expression régulière en général, je ne veux pas souligner que l'utilisation d'affirmations de limites de mots provoquerait un comportement indésirable dans Javascript. Et bien sûr, alors que les Javascripts sont généralement pour le navigateur, il y a aussi Node.
Donovan P