RegEx: Pobieranie wartości między znakami cudzysłowu

240

Mam taką wartość:

"Foo Bar" "Another Value" something else

Jakie wyrażenie regularne zwróci wartości ujęte w cudzysłów (np. Foo BarI Another Value)?

martwy błąd
źródło
Związane z stackoverflow.com/questions/138552/...
Andrew Edgecombe

Odpowiedzi:

361

Z dużym powodzeniem korzystam z następujących:

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

Obsługuje również zagnieżdżone cytaty.

Dla tych, którzy chcą głębsze wyjaśnienie jak to działa, oto wyjaśnienie od użytkownika ephemient :

([""'])dopasuj cytat; ((?=(\\?))\2.)jeśli istnieje ukośnik odwrotny, pożreć go i czy tak się stanie, dopasuj znak; *?dopasuj wiele razy (niechciwie, aby nie zjeść końcowego cytatu); \1dopasuj ten sam cytat, który został użyty do otwarcia.

Adam
źródło
6
@steve: to również zgodne, nieprawidłowo "foo\". Spojrzenie w przyszłość trik sprawia ?zaborczy kwantyfikatora (nawet jeśli aromat regex nie obsługuje ?+grupowanie składni lub atomowej)
Robin
1
W przypadku Pythona powoduje to błąd: sre_constants.error: nie można odwoływać się do otwartej grupy
a1an
9
Zwraca wartości, w tym pasujące cudzysłowy. Czy nie ma szansy na zwrócenie tylko treści między cytatami, zgodnie z żądaniem?
Martin Schneider,
4
Nadużywanie perspektywy jako zabierającego kwantyfikatora jest całkowicie niepotrzebne i dezorientujące. Wystarczy użyć alternacji:(["'])(?:\\.|[^\\])*?\1
Aran-Fey
2
jak uniknąć pustych ciągów?
Vikas Bansal
333

Zasadniczo szukasz następującego fragmentu wyrażenia regularnego:

"(.*?)"

To używa non-chciwego *? operator, aby uchwycić wszystko do następnego podwójnego cytatu, ale bez niego. Następnie używasz mechanizmu specyficznego dla języka, aby wyodrębnić dopasowany tekst.

W Pythonie możesz wykonać:

>>> import re
>>> string = '"Foo Bar" "Another Value"'
>>> print re.findall(r'"(.*?)"', string)
['Foo Bar', 'Another Value']
Greg Hewgill
źródło
11
Jest to świetne, jednak nie obsługuje ciągów znaków z cudzysłowami. np."hello \" world"
robbyt
Używając dopasowania JavaScript, będzie to również pasować do cudzysłowów. Będzie działał z iteracją po exec, jak opisano tutaj: stackoverflow.com/questions/7998180/…
Kiechlus
4
@robbyt Wiem, że jest trochę za późno na odpowiedź, ale co z negatywnym wyglądem? "(.*?(?<!\\))"
Mateus
4
Dziękuję - jest to prostsze, jeśli masz pewność, że nie ma uciekających ofert.
squarecandy
Jedno słowo. Niesamowite !
Shiva Avula
89

Wybrałbym:

"([^"]*)"

[^ „] Jest regex dla każdego znaku z wyjątkiem ' '
The powodu Używam tego przez operatora spoza chciwy wielu jest to, że muszę zachować się, że się po prostu upewnić, mogę to poprawić.

Martin York
źródło
1
Zachowuje się to również dobrze wśród różnych interpretacji wyrażeń regularnych.
Phil Bennett,
5
To uratowało mi zdrowie psychiczne. W implementacji RegNET dla .NET „(. *?)” Nie ma pożądanego efektu (nie działa chciwie), ale „([^”] *) ”ma.
Jens Neubauer
To jest najlepsza odpowiedź imo. Dzięki
Lmao 123
28

Zobaczmy dwa skuteczne sposoby radzenia sobie z cytowanymi cytatami. Wzory te nie mają być zwięzłe ani estetyczne, ale by były skuteczne.

Te sposoby wykorzystują dyskryminację pierwszego znaku, aby szybko znaleźć cudzysłowy w ciągu bez kosztów zmiany. (Chodzi o to, aby szybko odrzucić postacie, które nie są cudzysłowami, bez przetestowania dwóch gałęzi przemian.)

Treść między cytatami jest opisana za pomocą rozwiniętej pętli (zamiast powtarzanej naprzemiennej), aby również była bardziej wydajna: [^"\\]*(?:\\.[^"\\]*)*

Oczywiście, aby poradzić sobie z ciągami, które nie równoważą cytatów, możesz zamiast tego użyć kwantyfikatorów dzierżawczych: [^"\\]*+(?:\\.[^"\\]*)*+lub obejścia, aby je naśladować, aby zapobiec zbyt dużemu cofaniu się. Możesz również wybrać, że cytowana część może być cytatem otwierającym do następnego (bez zmiany znaczenia) cytatu lub końca łańcucha. W tym przypadku nie ma potrzeby używania kwantyfikatorów dzierżawczych, wystarczy, że ostatni cytat będzie opcjonalny.

Uwaga: czasami cytaty nie są poprzedzane odwrotnym ukośnikiem, ale przez powtórzenie cytatu. W tym przypadku subpattern treści wygląda następująco:[^"]*(?:""[^"]*)*

Wzorce unikają użycia grupy przechwytywania i odnośników wstecznych (mam na myśli coś podobnego (["']).....\1) i stosują prostą alternatywę, ale z ["']początkiem.

Perl lubi:

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

(zwróć uwagę, że (?s:...)jest to cukier syntaktyczny do włączania trybu dotall / singleline w grupie nie przechwytującej. Jeśli ta składnia nie jest obsługiwana, możesz łatwo włączyć ten tryb dla całego wzorca lub zastąpić kropkę [\s\S])

(Sposób, w jaki zapisany jest ten wzorzec, jest całkowicie „obsługiwany ręcznie” i nie uwzględnia ewentualnych wewnętrznych optymalizacji silnika)

Skrypt ECMA:

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

Rozszerzony POSIX:

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

lub po prostu:

"([^"\\]|\\.|\\\n)*"|'([^'\\]|\\.|\\\n)*'
Casimir et Hippolyte
źródło
1
Python akceptuje skrypt ECMA z nieprzetworzonym formatem ciągów, tzn. R „” „Skrypt ECMA” ”„
1
1
Jest to genialne, bardzo łatwo było dostosować ECMA do pracy z ucieczką nowej linii i powrotu karetki w podwójnych cudzysłowach.
Douglas Gaskell,
@ douglasg14b: Dzięki. Zauważ, że jeśli chcesz go używać w JavaScript, wystarczy użyć dosłownej notacji, /pattern/nie unikając niczego (zamiast notacji obiektowej new RegExp("(?=[\"'])(?:\"[^\"\\\\]*...");)
Casimir et Hippolyte
@ a1an: tak, ale możesz użyć wersji Perla, jeśli usuniesz stutaj: (?s:i umieścisz (?s)gdzieś we wzorcu.
Casimir et Hippolyte
16

RegEx zaakceptowanej odpowiedzi zwraca wartości, w tym ich cudzysłowy: "Foo Bar"i "Another Value"jako dopasowania.

Oto RegEx, które zwracają tylko wartości między znakami cudzysłowu (jak pytał pytający):

Tylko podwójne cudzysłowy (użyj wartości grupy przechwytywania nr 1):

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

Tylko pojedyncze cudzysłowy (użyj wartości grupy przechwytywania nr 1):

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

Oba (użyj wartości grupy przechwytywania nr 2):

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

-

Cała obsługa uciekała i zagnieżdżała cytaty.

Martin Schneider
źródło
Dlaczego to działa? Używałem src="(.*)", ale oczywiście to był wybierając wszystko przed ostatnim”Twój REGEX jednak wybrano tylko src =«»zawartość, ale nie rozumiem w jaki sposób?
Lucas Bustamante
Bardzo podoba mi się ten ze względu na prostotę, ale nie radzi sobie zbyt dobrze z pustymi lub bez wartości między cudzysłowami, jak odkryłem
RedactedProfile
16

W szczególności żadna z tych odpowiedzi nie powoduje wyrażenia regularnego, w którym zwróconym dopasowaniem jest tekst w cudzysłowie, o co jest proszony. MA-Madden próbuje, ale zdobywa walkę wewnętrzną jako złapana grupa, a nie cały mecz. Jednym ze sposobów na zrobienie tego byłoby:

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

Przykłady tego można zobaczyć w tym demo https://regex101.com/r/Hbj8aP/1

Kluczem jest tutaj pozytywny wygląd na początku ( ?<=) i pozytywny widok na końcu ( ?=). Lookbehind spogląda za obecną postacią, aby sprawdzić cytat, jeśli zostanie znaleziony, zacznij od niego, a następnie lookahead sprawdza postać przed cytatem, a jeśli zostanie znaleziony, zatrzymaj się na tej postaci. Grupa lookbehind ( ["']) jest owinięta w nawiasy kwadratowe, aby utworzyć grupę dla dowolnego cytatu znalezionego na początku, a następnie jest używana na końcu lookahead, (?=\1)aby upewnić się, że zatrzyma się tylko, gdy znajdzie odpowiedni cytat.

Jedyną inną komplikacją jest to, że ponieważ lookahead tak naprawdę nie zużywa cytatu końcowego, zostanie znaleziony ponownie przez początkowy lookbehind, co powoduje dopasowanie tekstu między końcowymi i początkowymi cytatami w tym samym wierszu. ["']\bPomaga to nałożenie granicy słów na cytat otwierający ( ), choć idealnie chciałbym przejść obok perspektywy, ale nie sądzę, aby było to możliwe. Trochę pozwalając na ucieczkę bohaterom w środku wziąłem bezpośrednio z odpowiedzi Adama.

IrishDubGuy
źródło
11

Bardzo późna odpowiedź, ale lubię odpowiadać

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

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

Suganthan Madhavan Pillai
źródło
Działa ładnie w php.
Parapluie
Jak dotąd jedyna odpowiedź na przechwycenie zarówno „strony głównej”: zlokalizuj [„stronę główną”] zlokalizuj [„stronę główną”]
jBelanger
8

Powyższy wzór (["'])(?:(?=(\\?))\2.)*?\1spełnia swoje zadanie, ale martwię się o jego występy (nie jest źle, ale mogłoby być lepiej). Moje poniżej jest ~ 20% szybsze.

Wzór "(.*?)"jest po prostu niepełny. Moja rada dla wszystkich, którzy to czytają, to NIE WYKORZYSTAJ GO !!!

Na przykład nie może przechwycić wielu ciągów (w razie potrzeby mogę dostarczyć wyczerpujący przypadek testowy), taki jak ten poniżej:

$ string = 'Jak się masz? Nic \'mi nie jest, dziękuję ”;

Reszta jest tak samo „dobra” jak ta powyżej.

Jeśli naprawdę zależy Ci na wydajności i precyzji, zacznij od tego poniżej:

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

W moich testach obejmował każdy napotkany ciąg, ale jeśli znajdziesz coś, co nie działa, chętnie bym go zaktualizował.

Sprawdź mój wzór w internetowym testerze wyrażeń regularnych .

Eugen Mihailescu
źródło
1
Podoba mi się prostota twojego wzoru, jednak pod względem wydajności wzór Casimira et Hippolyte wyrzuca wszystkie rozszerzone rozwiązania z wody. Co więcej, wygląda na to, że Twój wzór ma problemy z rozszerzonymi przypadkami na krawędzi, takimi jak cytat na końcu zdania.
wp78de
7

Podobało mi się rozwiązanie Eugena Mihailescu polegające na dopasowywaniu treści między cytatami przy jednoczesnym unikaniu cytatów. Jednak odkryłem pewne problemy z ucieczką i wymyśliłem następujący regex, aby je naprawić:

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

To załatwia sprawę i jest nadal dość prosty i łatwy w utrzymaniu.

Demo (z kilkoma więcej przypadków testowych; możesz go używać i rozszerzać).


PS: Jeśli chcesz tylko treść między cytatami w pełnym dopasowaniu ( $0) i nie boisz się kary za wyniki:

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

Niestety, bez cytatów jako kotwic, musiałem dodać granicę, \bktóra nie gra dobrze ze spacjami i znakami granicznymi niebędącymi słowami po cytacie początkowym.

Ewentualnie zmodyfikuj wersję początkową, po prostu dodając grupę i wyodrębnij ciąg znaków$2 :

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

PPS: Jeśli koncentrujesz się wyłącznie na wydajności, skorzystaj z rozwiązania Casimira et Hippolyte ; ten jest dobry.

wp78de
źródło
obserwacja: w drugim wyrażeniu regularnym brakuje wartości ze znakiem minus -, jak we współrzędnych długości geograficznej.
Crowcoder
Nic nie zmieniłem. Jeśli nie zauważysz problemu, może używam smaku wyrażenia regularnego. Korzystałem z regex101site, myślę, że regex w stylu php.
Crowcoder,
Oto demo tego, o czym mówię. Spodziewałem się, że będzie pasować do długości geograficznej (-96,74025), ale tak nie jest.
Crowcoder
@Crowcoder Dziękujemy. Tak, jest to spowodowane granicą słowa, która działa jak kotwica i pomaga uniknąć nakładania się dopasowań, ale nie gra się dobrze z twoimi danymi wejściowymi. Dodatkowa grupa jest w rzeczywistości lepszą opcją, jak zauważono w zaktualizowanej odpowiedzi.
wp78de
6

Ta wersja

  • konta dla uciekających cytatów
  • kontroluje cofanie

    /(["'])((?:(?!\1)[^\\]|(?:\\\\)*\\[^\\])*)\1/
Axeman
źródło
Obejmuje to wiele ciągów i wydaje się, że nie obsługuje poprawnie podwójnego ukośnika odwrotnego, na przykład ciąg: foo 'stri \\ ng 1' bar 'ciąg 2' i 'ciąg 3' Debuggex Demo
miracle2k
Nie można użyć odwołania wstecznego w klasie postaci.
HamZa
5

WIĘCEJ ODPOWIEDZI! Oto rozwiązanie, którego użyłem

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

TLDR;
zamień ikonę słowa na to, czego szukasz w wymienionych cytatach i voila!


Działa to tak, że szuka słowa kluczowego i nie obchodzi go, co jeszcze pomiędzy cudzysłowami. EG:
id="fb-icon"
id="icon-close"
id="large-icon-close"
regex szuka znaku cudzysłowu, "
a następnie szuka dowolnej możliwej grupy liter, która nie jest, "
dopóki nie znajdzie, icon
i każdej możliwej grupy liter, która nie "
jest, wtedy szuka zamknięcia"

James Harrington
źródło
1
Dziękuję Ci bardzo. był w stanie zastąpić wszystkie wystąpienia name="value"z name={"value"}ponieważ regex zwrotów ta odpowiedź w icon/ valuew drugiej grupie (w przeciwieństwie do akceptowanej odpowiedzi). Znajdź : =\"([^\"]*?[^\"]*?)\" Zamień :={"$1"}
Palisand
Masz ochotę wyjaśnić opinię? działa dobrze w niektórych sytuacjach.
James Harrington,
Odpowiadasz mi?
Palisand
@Palisand nikt nie głosował tego postu następnego dnia bez wyjaśnienia.
James Harrington,
wydaje się, że jest to jedyna odpowiedź, która znajduje konkretny tekst w cudzysłowie
Top-Master
4

Podobała mi się bardziej ekspansywna wersja Axemana, ale miałem z nią pewne problemy (na przykład nie pasowała

foo "string \\ string" bar

lub

foo "string1"   bar   "string2"

poprawnie, więc próbowałem to naprawić:

# 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
źródło
3
string = "\" foo bar\" \"loloo\""
print re.findall(r'"(.*?)"',string)

po prostu wypróbuj to, działa jak urok !!!

\ oznacza pominięcie znaku

mobman
źródło
Jeśli ten pierwszy wiersz jest rzeczywistym kodem Pythona, utworzy ciąg " foo bar" "loloo". Podejrzewam, że chodziło owinąć że w surowym sznurkiem jak zrobiłeś z regex: r'"\" foo bar\" \"loloo\""'. Proszę korzystać z doskonałych możliwości formatowania SO, gdy jest to właściwe. To nie tylko kosmetyki; dosłownie nie możemy powiedzieć, co próbujesz powiedzieć, jeśli ich nie używasz. Witamy w Stack Overflow !
Alan Moore
dziękuję za radę, Alan, właściwie jestem nowy w tej społeczności, następnym razem na pewno będę o tym pamiętać ... szczere przeprosiny.
mobman
2

W przeciwieństwie do odpowiedzi Adama mam prostą, ale działającą:

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

I po prostu dodaj nawias, jeśli chcesz uzyskać treść w takich cytatach:

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

Następnie $1dopasowuje znak cytowania i $2dopasowuje ciąg treści.

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

Spowoduje to:> Foo Bar <> <> ale to <

Tutaj pokazałem łańcuch wyników między> <dla jasności, również używając wersji innej niż chciwa z tym poleceniem sed, najpierw wyrzucamy śmieci przed i po "", a następnie zamieniamy na część między "" i otaczaj to przez> <'s.

amo-ej1
źródło
1

Od Grega H. Byłem w stanie stworzyć ten regex zgodnie z moimi potrzebami.

Musiałem dopasować określoną wartość, która została zakwalifikowana przez umieszczenie w cudzysłowie. Musi to być pełny mecz, żadne częściowe dopasowanie nie powinno spowodować trafienia

np. „test” nie może pasować do „test2”.

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

myśliwy

motoprog
źródło
1

Jeśli próbujesz znaleźć ciągi, które mają tylko określony sufiks, na przykład składnię kropkową, możesz spróbować:

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

Gdzie .localizedjest przyrostek.

Przykład:

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

Będzie przechwytywał "this is something I need to return".localizedi "so is this".localizednie "but this is not".

OffensivelyBad
źródło
1

Dodatkowa odpowiedź dla podzbioru koderów Microsoft VBA tylko jeden korzysta z biblioteki, Microsoft VBScript Regular Expressions 5.5co daje następujący kod

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
źródło
0

Dla mnie pracował ten:

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

Użyłem zdania takiego jak ten:

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

i działało świetnie.

Alexandru Furculita
źródło
Słabość tego podejścia polega na tym, że będzie pasować, gdy łańcuch zaczyna się od pojedynczego cytatu, a kończy podwójnym cytatem, lub odwrotnie.
Ghopper21
Ma również problemy z złapaniem „Nie zapomnij @” - zatrzymuje się po „Don”.
Benny Neugebauer
0

Wszystkie powyższe odpowiedzi są dobre .... poza tym, że NIE obsługują wszystkich znaków Unicode! w ECMA Script (Javascript)

Jeśli jesteś użytkownikiem Węzła, możesz chcieć zmodyfikowanej wersji zaakceptowanej odpowiedzi, która obsługuje wszystkie znaki Unicode:

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

Spróbuj tutaj .

Donovan P.
źródło
1
Co to jest znak inny niż Unicode? Unicode AFAIK obejmuje wszystkie znaki.
Toto
1
Dlaczego zgadujesz, że to pytanie javascript? Co więcej, lookbehind nie jest obsługiwany we wszystkich przeglądarkach, rzuty regex101? The preceding token is not quantifiable
Toto
@Toto, mam na myśli to, że „nie obsługuje wszystkich znaków Unicode”. Dziękuję Ci. Podczas gdy pytanie dotyczy ogólnie wyrażenia regularnego, nie chcę po prostu podkreślać, że użycie asercji granicy słowa spowoduje niepożądane zachowanie w JavaScript. I oczywiście, podczas gdy Javascripts są ogólnie dla przeglądarki, istnieje również Node.
Donovan P