RegEx: acquisizione di valori tra virgolette


Risposte:


361

Sto usando quanto segue con grande successo:

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

Supporta anche le virgolette nidificate.

Per coloro che desiderano una spiegazione più approfondita di come funziona, ecco una spiegazione da parte dell'utente effimero :

([""'])abbinare un preventivo; ((?=(\\?))\2.)se esiste una barra rovesciata, divorala e, se ciò accade, abbina un personaggio; *?abbinare più volte (non avidamente, per non mangiare la citazione di chiusura); \1corrisponde alla stessa citazione utilizzata per l'apertura.


6
@steve: questo sarebbe anche corrispondere, in modo non corretto, "foo\". Il trucco del futuro rende ?possessivo il quantificatore (anche se il sapore regex non supporta la ?+sintassi o il raggruppamento atomico)
Robin,

1
Con Python viene generato un errore: sre_constants.error: impossibile fare riferimento al gruppo aperto
a1an

9
Ciò restituisce i valori comprese le virgolette corrispondenti. Non c'è possibilità di restituire solo il contenuto tra le virgolette, come è stato richiesto?
Martin Schneider,

4
Abusare di uno sguardo come un quantificatore possessivo è completamente inutile e confuso. Usa solo un'alternativa:(["'])(?:\\.|[^\\])*?\1
Aran-Fey,

2
come evitare le stringhe vuote?
Vikas Bansal,

333

In generale, il seguente frammento di espressione regolare è ciò che stai cercando:

"(.*?)"

Questo utilizza il non avido *? operatore per catturare tutto fino a ma senza includere la prossima doppia citazione. Quindi, si utilizza un meccanismo specifico della lingua per estrarre il testo corrispondente.

In Python, puoi fare:

>>> import re
>>> string = '"Foo Bar" "Another Value"'
>>> print re.findall(r'"(.*?)"', string)
['Foo Bar', 'Another Value']

11
Questo è fantastico, tuttavia non gestisce le stringhe con virgolette di escape. ad es."hello \" world"
Robbyt,

Usando la corrispondenza di JavaScript, questo corrisponderà anche alle virgolette. Si lavorerà con l'iterazione su exec come descritto qui: stackoverflow.com/questions/7998180/...
Kiechlus

4
@robbyt So che è un po 'tardi per una risposta, ma che ne dici di un aspetto negativo? "(.*?(?<!\\))"
Mateus,

4
Grazie - questo è più semplice se sei sicuro che non ci siano virgolette sfuggite da affrontare.
squarecandy,

Una parola. Eccezionale !
Shiva Avula,

89

Vorrei andare per:

"([^"]*)"

Il [^ "] è regex per qualsiasi personaggio tranne ' " '
Il motivo per cui lo uso su molti operatori non avidi è che devo continuare a cercarlo solo per assicurarmi di averlo corretto.


1
Questo si comporta bene anche tra le diverse interpretazioni regex.
Phil Bennett,

5
Questo ha salvato la mia sanità mentale. Nell'implementazione RegEx di .NET, "(. *?)" Non ha l'effetto desiderato (non agisce in modo non avido), ma "([^"] *) ".
Jens Neubauer,

Questa è la migliore risposta imo. Grazie
Lmao 123

28

Vediamo due modi efficaci per gestire le citazioni di escape. Questi modelli non sono progettati per essere concisi né estetici, ma per essere efficienti.

In questi modi viene utilizzata la prima discriminazione di caratteri per trovare rapidamente le virgolette nella stringa senza il costo di un'alternanza. (L'idea è di scartare rapidamente i caratteri che non sono virgolette senza testare i due rami dell'alternanza.)

Il contenuto tra virgolette viene descritto con un ciclo non srotolato (anziché una ripetuta alternanza) per essere anche più efficiente: [^"\\]*(?:\\.[^"\\]*)*

Ovviamente per gestire stringhe che non hanno virgolette bilanciate, puoi usare invece quantificatori possessivi: [^"\\]*+(?:\\.[^"\\]*)*+o una soluzione alternativa per emularli, per evitare troppi backtracking. Puoi anche scegliere che una parte tra virgolette possa essere una virgoletta di apertura fino alla virgoletta successiva (senza escape) o alla fine della stringa. In questo caso non è necessario utilizzare quantificatori possessivi, è sufficiente rendere facoltativo l'ultimo preventivo.

Avviso: a volte le virgolette non sono sfuggite a una barra rovesciata ma ripetendo la citazione. In questo caso il sottotitolo del contenuto è simile al seguente:[^"]*(?:""[^"]*)*

I modelli evitano l'uso di un gruppo di acquisizione e un backreference (intendo qualcosa di simile (["']).....\1) e usano una semplice alternanza ma con ["']all'inizio, in fattore.

Perl piace:

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

(si noti che (?s:...)è uno zucchero sintattico per attivare la modalità dotall / singleline all'interno del gruppo non di acquisizione. Se questa sintassi non è supportata, è possibile attivare facilmente questa modalità per tutto il modello o sostituire il punto con [\s\S])

(Il modo in cui questo modello è scritto è totalmente "guidato dalla mano" e non tiene conto di eventuali ottimizzazioni interne del motore)

Sceneggiatura ECMA:

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

POSIX esteso:

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

o semplicemente:

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

1
Python accetta lo script ECMA con formato stringa non
elaborato

1
Questo è geniale, è stato molto facile adattare il tuo ECMA per lavorare con la fuga di nuove righe e ritorni a capo tra virgolette doppie.
Douglas Gaskell,

@ douglasg14b: grazie. Nota che se vuoi usarlo in Javascript, devi solo usare la notazione letterale /pattern/senza sfuggire a nulla (invece della notazione dell'oggetto new RegExp("(?=[\"'])(?:\"[^\"\\\\]*...");)
Casimir et Hippolyte

@ a1an: sì, ma puoi usare la versione Perl se rimuovi squi: (?s:e se metti (?s)da qualche parte nel modello.
Casimir et Hippolyte,

16

Il RegEx della risposta accettata restituisce i valori comprese le virgolette circostanti: "Foo Bar"e "Another Value"come corrispondenze.

Ecco RegEx che restituisce solo i valori tra virgolette (come stava chiedendo l'interrogatore):

Solo virgolette doppie (utilizzare il valore del gruppo di acquisizione n. 1):

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

Solo virgolette singole (utilizzare il valore del gruppo di acquisizione n. 1):

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

Entrambi (utilizzare il valore del gruppo di acquisizione n. 2):

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

-

Tutte le virgolette di escape e nidificate supportano.


Per favore, perché funziona? Stavo usando src="(.*)"ma ovviamente stava selezionando tutto prima dell'ultimo ", tuttavia il tuo REGEX ha selezionato solo i contenuti src =" ", ma non ho capito come?
Lucas Bustamante,

Mi piace molto questo per la sua semplicità, ma non gestisce molto bene il valore vuoto o nessun valore tra virgolette, come ho scoperto
RedactedProfile

16

Stranamente, nessuna di queste risposte produce una regex in cui la corrispondenza restituita è il testo all'interno delle virgolette, che è ciò che viene richiesto. MA-Madden ci prova ma ottiene solo la partita interna come gruppo catturato piuttosto che l'intera partita. Un modo per farlo effettivamente sarebbe:

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

Esempi per questo possono essere visti in questa demo https://regex101.com/r/Hbj8aP/1

La chiave qui è lo sguardo positivo all'inizio (il ?<=) e lo sguardo positivo alla fine (il ?=). Il lookbehind sta guardando dietro il personaggio attuale per cercare una citazione, se trovato inizia da lì e poi il lookahead sta controllando il personaggio in anticipo per una citazione e se trovato si ferma su quel personaggio. Il gruppo lookbehind (il ["']) è racchiuso tra parentesi quadre per creare un gruppo per qualsiasi citazione trovata all'inizio, che viene quindi utilizzata alla fine del lookahead (?=\1)per assicurarsi che si fermi solo quando trova la citazione corrispondente.

L'unica altra complicazione è che, poiché il lookahead non consuma effettivamente la virgoletta finale, verrà ritrovato dal lookbehind iniziale che fa corrispondere il testo tra virgolette finali e iniziali sulla stessa riga. Mettere un limite di parola nella citazione iniziale ( ["']\b) aiuta in questo, anche se idealmente mi piacerebbe passare oltre il lookahead ma non penso che sia possibile. La parte che permette ai personaggi fuggiti nel mezzo l'ho presa direttamente dalla risposta di Adam.



8

Lo schema (["'])(?:(?=(\\?))\2.)*?\1sopra fa il lavoro ma sono preoccupato per le sue prestazioni (non è male ma potrebbe essere migliore). Il mio sotto è ~ 20% più veloce.

Il modello "(.*?)"è solo incompleto. Il mio consiglio per tutti coloro che leggono questo è NON USARLO !!!

Ad esempio, non è in grado di catturare molte stringhe (se necessario posso fornire un esaustivo test-case) come quello qui sotto:

$ string = 'Come stai? Sto \'bene, grazie ';

Gli altri sono "buoni" come quelli sopra.

Se ti interessano davvero sia le prestazioni che la precisione, inizia con quello qui sotto:

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

Nei miei test ha riguardato tutte le stringhe che ho incontrato, ma se trovi qualcosa che non funziona lo aggiornerei volentieri per te.

Controlla il mio modello in un tester regex online .


1
Mi piace la semplicità del tuo modello, tuttavia il modello Casimir et Hippolyte per quanto riguarda le prestazioni fa esplodere tutte le soluzioni estese. Inoltre, sembra che il tuo modello abbia problemi con casi limite estesi come una citazione sfuggita alla fine della frase.
wp78de,

7

Mi è piaciuta la soluzione di Eugen Mihailescu per abbinare il contenuto tra virgolette pur consentendo di sfuggire alle citazioni. Tuttavia, ho scoperto alcuni problemi con l'escaping e ho trovato la seguente regex per risolverli:

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

Fa il trucco ed è ancora piuttosto semplice e facile da mantenere.

Demo (con alcuni altri casi di test; sentiti libero di usarlo ed espanderlo).


PS: se vuoi solo il contenuto tra virgolette nell'intera corrispondenza ( $0) e non hai paura del penalità prestazionale, usa:

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

Sfortunatamente, senza le virgolette come ancore, ho dovuto aggiungere un limite \bche non gioca bene con spazi e caratteri di confine non parole dopo la citazione iniziale.

In alternativa, modifica la versione iniziale semplicemente aggiungendo un gruppo ed estraendo il modulo stringa$2 :

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

PPS: se il tuo focus è esclusivamente sull'efficienza, scegli la soluzione di Casimir et Hippolyte ; è buono.


osservazione: il secondo regex manca un valore con un segno meno -, come nelle coordinate di longitudine.
Crowcoder

Non ho cambiato nulla. Se non osservi il problema, forse è il sapore di regex che sto usando. Stavo usando il sito regex101, penso che regex in stile php.
Crowcoder

Ecco la demo di ciò di cui sto parlando. Mi aspettavo che corrispondesse alla longitudine (-96.74025) ma non è così.
Crowcoder,

@Crowcoder Grazie. Sì, questo è causato dalla parola limite che funge da punto di ancoraggio e aiuta ad evitare sovrapposizioni di partite ma non gioca bene con i tuoi input. Un gruppo aggiuntivo è in realtà l'opzione migliore, come indicato nella risposta aggiornata.
wp78de,

6

Questa versione

  • conti per le citazioni di escape
  • controlla il backtracking

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

Questo si estende su più stringhe e non sembra gestire correttamente una doppia barra rovesciata, ad esempio la stringa: foo 'stri \\ ng 1' bar 'string 2' e 'string 3' Debuggex Demo
miracle2k

Non è possibile utilizzare un backreference in una classe di caratteri.
HamZa,

5

ALTRE RISPOSTE! Ecco la soluzione che ho usato

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

TLDR;
sostituisci l' icona della parola con quello che cerchi tra citazioni e voilà!


Il modo in cui funziona è che cerca la parola chiave e non importa cos'altro tra le virgolette. Ad esempio:
id="fb-icon"
id="icon-close"
id="large-icon-close"
il regex cerca un segno di virgolette, "
quindi cerca ogni possibile gruppo di lettere che non "
arriva fino a quando non trova icon
e ogni possibile gruppo di lettere che non "
lo è, quindi cerca una chiusura"


1
Grazie mille. è stato in grado di sostituire ogni ricorrenza di name="value"con name={"value"}poiché la regex di questa risposta ritorna icon/ valuecome secondo gruppo (a differenza della risposta accettata). Trova : =\"([^\"]*?[^\"]*?)\" Sostituisci :={"$1"}
Palisand,

Ti dispiace spiegare il downvote? funziona bene da alcune situazioni.
James Harrington,

Mi stai rispondendo?
Palisand,

@Palisand nessuno ha votato in negativo questo post l'altro giorno senza alcuna spiegazione.
James Harrington,

questa sembra essere l'unica risposta che trova un testo specifico tra virgolette
Top-Master il

4

Mi è piaciuta la versione più espansiva di Axeman, ma ho avuto qualche problema con esso (non corrispondeva ad esempio

foo "string \\ string" bar

o

foo "string1"   bar   "string2"

correttamente, quindi ho provato a risolverlo:

# 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

3
string = "\" foo bar\" \"loloo\""
print re.findall(r'"(.*?)"',string)

prova questo, funziona come un incanto !!!

\ indica il carattere di salto


Se quella prima riga è l'attuale codice Python, creerà la stringa " foo bar" "loloo". Ho il sospetto che si intende per avvolgere che in una stringa cruda come avete fatto con la regex: r'"\" foo bar\" \"loloo\""'. Si prega di utilizzare le eccellenti capacità di formattazione di SO ogni volta che è appropriato. Non sono solo cosmetici; non possiamo letteralmente dire cosa stai cercando di dire se non li usi. E benvenuti a Stack Overflow !
Alan Moore,

grazie per il consiglio, alan, in realtà sono nuovo di questa community, la prossima volta terrò sicuramente a mente tutto questo ... scuse sincere.
mobman,

2

A differenza della risposta di Adam, ne ho una semplice ma funzionante:

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

E aggiungi solo parentesi se vuoi ottenere contenuti tra virgolette come questo:

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

Quindi $1abbina il carattere preventivo e $2corrisponde alla stringa di contenuto.


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

Ciò comporterà:> Foo Bar <> <> ma questo <

Qui ho mostrato la stringa di risultato tra> <'s per chiarezza, anche usando la versione non avida con questo comando sed prima buttiamo via la spazzatura prima e dopo quella "" e poi la sostituiamo con la parte tra "" e circondalo con> <'s.


1

Da Greg H. sono stato in grado di creare questa regex per soddisfare le mie esigenze.

Avevo bisogno di abbinare un valore specifico che si qualificava tra virgolette. Deve essere una partita completa, nessuna corrispondenza parziale potrebbe innescare un colpo

ad es. "test" non può corrispondere a "test2".

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

Cacciatore


1

Se stai cercando di trovare stringhe che hanno solo un certo suffisso, come la sintassi del punto, puoi provare questo:

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

Dove .localized il suffisso.

Esempio:

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

Catturerà "this is something I need to return".localizede "so is this".localizednon "but this is not".


1

Una risposta supplementare per il sottoinsieme di programmatori Microsoft VBA utilizza solo una libreria Microsoft VBScript Regular Expressions 5.5e ciò fornisce il seguente codice

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

0

Per me ha funzionato questo:

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

Ho usato in una frase come questa:

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

e ha funzionato alla grande.


Un punto debole di questo approccio è che corrisponderà quando una stringa inizia con una virgoletta singola e termina con una virgoletta doppia o viceversa.
Ghopper21,

Ha anche problemi a catturare "Non dimenticare la @" - Si interrompe dopo "Don".
Benny Neugebauer,

0

Tutte le risposte sopra sono buone .... tranne che NON supportano tutti i caratteri Unicode! presso ECMA Script (Javascript)

Se sei un utente Node, potresti desiderare la versione modificata della risposta accettata che supporti tutti i caratteri Unicode:

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

Prova qui .


1
Che cos'è un carattere non unicode? L'unicode AFAIK copre tutto il personaggio.
Tot

1
Perché pensi che sia una domanda javascript? Inoltre, lookbehind non è supportato in tutti i browser, regex101 genera? The preceding token is not quantifiable
Toto

@Toto, quello che voglio dire è "non supporta tutto il carattere Unicode". Grazie. Mentre la domanda riguarda regex in generale, non voglio solo sottolineare che l'uso di asserzioni al contorno di parole causerebbe comportamenti indesiderati nel Javascript. E ovviamente, mentre i Javascripts sono generalmente per browser, c'è anche Node.
Donovan P,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.