RegEx: agarrando valores entre aspas

240

Eu tenho um valor como este:

"Foo Bar" "Another Value" something else

Qual regex retornará os valores entre aspas (por exemplo, Foo Bare Another Value)?

deadbug
fonte
Relacionado a stackoverflow.com/questions/138552/…
Andrew Edgecombe

Respostas:

361

Eu tenho usado o seguinte com grande sucesso:

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

Ele também suporta aspas aninhadas.

Para aqueles que desejam uma explicação mais profunda de como isso funciona, aqui está uma explicação do efemiente do usuário :

([""'])corresponder a uma cotação; ((?=(\\?))\2.)se a barra invertida existir, engula e, se isso acontece ou não, combine um caractere; *?corresponder muitas vezes (sem avidez, para não comer a citação de fechamento); \1corresponder à mesma citação usada para a abertura.

Adão
fonte
6
@ Steve: isso também corresponderia, incorretamente "foo\",. O truque frente olhar faz com que o ?possessivo quantificador (mesmo se o sabor de regex não suporta a ?+sintaxe ou atômica agrupamento)
Robin
1
Com python, isso gera um erro: sre_constants.error: não pode se referir ao grupo aberto
a1an
9
Isso retorna os valores, incluindo as aspas correspondentes. Não há chance de retornar apenas o conteúdo entre as aspas, conforme solicitado?
Martin Schneider
4
Abusar de um lookahead como um quantificador possessivo é completamente desnecessário e confuso. Basta usar uma alternância:(["'])(?:\\.|[^\\])*?\1
Aran-Fey
2
como evitar seqüências de caracteres vazias?
Vikas Bansal
333

Em geral, o seguinte fragmento de expressão regular é o que você está procurando:

"(.*?)"

Isso usa o não-ganancioso *? operador para capturar tudo, mas sem incluir a próxima cotação dupla. Em seguida, você usa um mecanismo específico do idioma para extrair o texto correspondente.

No Python, você pode fazer:

>>> import re
>>> string = '"Foo Bar" "Another Value"'
>>> print re.findall(r'"(.*?)"', string)
['Foo Bar', 'Another Value']
Greg Hewgill
fonte
11
Isso é ótimo, no entanto, ele não manipula seqüências de caracteres com aspas escapadas. por exemplo,"hello \" world"
robbyt 5/02
Usando a correspondência do JavaScript, isso também corresponderá às aspas. Ele vai trabalhar com iteração sobre exec como descrito aqui: stackoverflow.com/questions/7998180/...
Kiechlus
4
@robbyt Eu sei que é um pouco tarde para uma resposta, mas que tal um olhar negativo por trás? "(.*?(?<!\\))"
Mateus
4
Obrigado - isso é mais simples se você tiver certeza de que não há aspas escapadas para lidar.
Squarecandy
Uma palavra. Impressionante !
Shiva Avula
89

Eu iria para:

"([^"]*)"

O [^ "] é regex para qualquer caractere, exceto ' " '
A razão pela qual eu uso isso em muitos operadores não gananciosos é que tenho que continuar pesquisando isso apenas para ter certeza de que está correto.

Martin York
fonte
1
Isso também se comporta bem entre diferentes interpretações de expressões regulares.
Phil Bennett
5
Isso salvou minha sanidade. Na implementação do RegEx do .NET, "(. *?)" Não tem o efeito desejado (não age de maneira não gananciosa), mas "([^"] *) "possui.
Jens Neubauer
Esta é a melhor resposta imo. Obrigado
Lmao 123
28

Vamos ver duas maneiras eficientes de lidar com aspas escapadas. Esses padrões não são projetados para serem concisos nem estéticos, mas para serem eficientes.

Essas maneiras usam a discriminação do primeiro caractere para encontrar rapidamente aspas na string sem o custo de uma alternância. (A idéia é descartar rapidamente caracteres que não são aspas sem testar os dois ramos da alternância.)

O conteúdo entre aspas é descrito com um loop desenrolado (em vez de uma alternância repetida) para ser mais eficiente também: [^"\\]*(?:\\.[^"\\]*)*

Obviamente, para lidar com seqüências de caracteres que não possuem aspas balanceadas, você pode usar quantificadores possessivos: [^"\\]*+(?:\\.[^"\\]*)*+ou uma solução alternativa para emulá-los, para evitar muitos retrocessos. Você também pode escolher que uma parte entre aspas possa ser uma cotação de abertura até a próxima cotação (sem escape) ou o final da sequência. Nesse caso, não há necessidade de usar quantificadores possessivos, você só precisa tornar a última cotação opcional.

Aviso: às vezes as aspas não são escapadas com uma barra invertida, mas repetindo-a. Nesse caso, o subpadrão de conteúdo fica assim:[^"]*(?:""[^"]*)*

Os padrões evitam o uso de um grupo de captura e uma referência anterior (quero dizer algo como (["']).....\1) e usam uma alternância simples, mas com ["']no início, em fator.

Perl gosta:

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

(observe que (?s:...)é um açúcar sintático para ativar o modo dotall / linha única dentro do grupo que não captura. Se essa sintaxe não for suportada, você poderá facilmente ativar esse modo para todo o padrão ou substituir o ponto [\s\S])

(A maneira como esse padrão é escrito é totalmente "manual" e não leva em consideração as eventuais otimizações internas do mecanismo)

Script ECMA:

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

POSIX estendido:

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

ou simplesmente:

"([^"\\]|\\.|\\\n)*"|'([^'\\]|\\.|\\\n)*'
Casimir et Hippolyte
fonte
1
O Python aceita o script ECMA com formato de string bruto, ou seja, r "" "ECMA script" ""
a1an
1
Isso é brilhante, foi muito fácil adaptar seu ECMA para trabalhar com escapamentos de nova linha e retornos de carro entre aspas duplas.
Douglas Gaskell
@ douglasg14b: Obrigado. Note que se você quiser usá-lo em Javascript, você só precisa usar a notação literal /pattern/sem escapar nada (em vez da notação de objeto new RegExp("(?=[\"'])(?:\"[^\"\\\\]*...");)
Casimir et Hippolyte
@ a1an: yes, mas você pode usar a versão Perl se você remover o shere: (?s:e se você colocar (?s)em algum lugar do padrão.
Casimir et Hippolyte
16

O RegEx da resposta aceita retorna os valores, incluindo suas aspas: "Foo Bar"e "Another Value"como correspondências.

Aqui estão RegEx que retornam apenas os valores entre aspas (como o questionador estava pedindo):

Somente aspas duplas (use o valor do grupo de captura nº 1):

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

Somente aspas simples (use o valor do grupo de captura nº 1):

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

Ambos (use o valor do grupo de captura nº 2):

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

-

Todo o suporte escapou e citações aninhadas.

Martin Schneider
fonte
Por favor, por que isso funciona? Eu estava usando src="(.*)", mas, obviamente, ele estava selecionando tudo antes da última", o seu REGEX, porém, selecionado apenas o src = '' conteúdo, mas eu não entendia como?
Lucas Bustamante
I como este muito por sua simplicidade, mas ele não controla vazia ou nenhum valor entre aspas muito bem como eu descobri
RedactedProfile
16

Curiosamente, nenhuma dessas respostas produz um regex em que a correspondência retornada é o texto dentro das aspas, o que é solicitado. MA-Madden tenta, mas apenas recebe a partida interna como um grupo capturado, e não a partida inteira. Uma maneira de fazer isso seria:

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

Exemplos para isso podem ser vistos nesta demonstração https://regex101.com/r/Hbj8aP/1

A chave aqui é o olhar positivo por trás no início (o ?<=) e o olhar positivo no final (o ?=). O lookbehind está olhando por trás do caractere atual para procurar uma cotação, se encontrado, em seguida, começa a partir daí e o lookahead verifica o personagem à frente para obter uma cotação e, se encontrado, interrompe esse caractere. O grupo lookbehind (the ["']) é colocado entre colchetes para criar um grupo para a citação que foi encontrada no início, depois é usada no final da lookahead (?=\1)para garantir que só pare quando encontrar a citação correspondente.

A única outra complicação é que, como a cabeça de impressão realmente não consome a citação final, ela será encontrada novamente pela aparência inicial, que faz com que o texto entre aspas finais e iniciais na mesma linha seja correspondido. Colocar um limite de palavras na citação de abertura ( ["']\b) ajuda com isso, embora, idealmente, eu gostaria de ir além da aparência, mas não acho que isso seja possível. A parte que permite caracteres de escape no meio, tirei diretamente da resposta de Adam.

IrishDubGuy
fonte
11

Uma resposta muito tarde, mas gostaria de responder

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

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

Suganthan Madhavan Pillai
fonte
Funciona muito bem em php.
Parapluie
A única resposta até agora para capturar "Página inicial" em: localizar ["Página inicial"] localizar ["Página inicial"]
jBelanger
8

O padrão (["'])(?:(?=(\\?))\2.)*?\1acima faz o trabalho, mas estou preocupado com o desempenho (não é ruim, mas poderia ser melhor). Mina abaixo é ~ 20% mais rápido.

O padrão "(.*?)"está incompleto. Meu conselho para todos que estão lendo isso é apenas NÃO O USE !!!

Por exemplo, ele não pode capturar muitas strings (se necessário, posso fornecer um caso de teste exaustivo) como o abaixo:

$ string = 'Como você está? Estou \'bem, obrigado ';

O resto deles é tão "bom" quanto o acima.

Se você realmente se importa com desempenho e precisão, comece com o abaixo:

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

Nos meus testes, abrangia todas as strings que conheci, mas se você encontrar algo que não funcione, eu o atualizaria com prazer.

Verifique meu padrão em um testador de regex online .

Eugen Mihailescu
fonte
1
Gosto da simplicidade do seu padrão, no entanto, o padrão de Casimir et Hippolyte em termos de desempenho tira todas as soluções estendidas da água. Além disso, parece que seu padrão tem problemas com casos extremos estendidos, como uma citação escapada no final da frase.
Wp78de 13/0518
7

Gostei da solução de Eugen Mihailescu para combinar o conteúdo entre aspas, permitindo escapar das aspas. No entanto, descobri alguns problemas com o escape e criei o seguinte regex para corrigi-los:

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

Ele faz o truque e ainda é bastante simples e fácil de manter.

Demonstração (com mais alguns casos de teste; fique à vontade para usá-lo e expandi-lo).


PS: Se você deseja apenas o conteúdo entre aspas na correspondência completa ( $0) e não tem medo da penalidade de desempenho, use:

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

Infelizmente, sem as aspas como âncoras, tive que adicionar um limite \bque não funcione bem com espaços e caracteres de limite que não sejam palavras após a citação inicial.

Como alternativa, modifique a versão inicial simplesmente adicionando um grupo e extraia o formulário da string$2 :

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

PPS: Se o seu foco é exclusivamente a eficiência, siga a solução de Casimir et Hippolyte ; é um bom.

wp78de
fonte
observação: o segundo regex perde um valor com um sinal de menos -, como nas coordenadas de longitude.
Crowcoder
Eu não mudei nada. Se você não observar o problema, talvez seja o sabor do regex que estou usando. Eu estava usando o regex101site, acho que o regex estilo php.
Crowcoder
Aqui está a demonstração do que estou falando. Eu esperava que correspondesse à longitude (-96,74025), mas não corresponde.
Crowcoder
@Crowcoder Obrigado. Sim, isso é causado pelo limite da palavra que atua como uma âncora e ajuda a evitar correspondências sobrepostas, mas não é agradável com sua entrada. Um grupo adicional é realmente a melhor opção, conforme observado na resposta atualizada.
wp78de
6

Esta versão

  • contas de cotações de escape
  • controla o retorno

    /(["'])((?:(?!\1)[^\\]|(?:\\\\)*\\[^\\])*)\1/
Axeman
fonte
Isso abrange várias seqüências de caracteres e parece não manipular uma barra invertida dupla corretamente, por exemplo, a string: foo 'stri \\ ng 1' bar 'string 2' e 'string 3' Debuggex Demo
miracle2k
Você não pode usar uma referência anterior em uma classe de personagem.
Hamza
5

MAIS RESPOSTAS! Aqui está a solução que eu usei

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

TLDR;
substitua o ícone da palavra pelo que você está procurando nas citações e pronto!


A maneira como isso funciona é a procura pela palavra-chave e não se importa com o que mais há entre as aspas. EG:
id="fb-icon"
id="icon-close"
id="large-icon-close"
o regex procura uma marca de citação "
e, em seguida, procura qualquer grupo possível de letras que não seja "
até encontrar icon
e qualquer grupo possível de letras que não "
seja, então busca um fechamento"

James Harrington
fonte
1
Muito obrigado. foi capaz de substituir todas as ocorrências de name="value"com, name={"value"}já que o regex dessa resposta retorna icon/ valuecomo o segundo grupo (diferentemente da resposta aceita). Encontre : =\"([^\"]*?[^\"]*?)\" Substitua :={"$1"}
Palisand
Se importa em explicar o voto negativo? funciona bem em algumas situações.
James Harrington
Você está me respondendo?
Palisand
@Palis e ninguém votou negativamente neste post no outro dia, sem explicação.
James Harrington
esta parece ser a única resposta que encontra um texto específico dentro de aspas
Top-Master
4

Eu gostei da versão mais abrangente do Axeman, mas tive alguns problemas com ela (não combinava, por exemplo,

foo "string \\ string" bar

ou

foo "string1"   bar   "string2"

corretamente, então tentei corrigi-lo:

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

apenas tente isso, funciona como um encanto !!!

\ indica pular caractere

mobman
fonte
Se essa primeira linha for o código Python real, ele criará a string " foo bar" "loloo". Eu suspeito que você significou para dispor que em uma corda crua como você fez com o regex: r'"\" foo bar\" \"loloo\""'. Utilize os excelentes recursos de formatação do SO sempre que apropriado. Não são apenas cosméticos; literalmente, não podemos dizer o que você está tentando dizer se não os usar. E bem-vindo ao Stack Overflow !
Alan Moore
obrigado pelo conselho alan, sou realmente novo nessa comunidade, da próxima vez certamente vou manter tudo isso em mente ... desculpas sinceras.
mobman
2

Diferentemente da resposta de Adam, eu tenho uma simples, mas que funcionou:

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

E adicione parênteses se quiser obter conteúdo entre aspas como este:

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

Em seguida, $1corresponde ao caractere de cotação e à $2sequência de caracteres do conteúdo.

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

Isso resultará em:> Foo Bar <> <> mas isso <

Aqui eu mostrei a sequência de resultados entre> <'s para maior clareza, também usando a versão não gananciosa com este comando sed, nós jogamos fora o lixo antes e depois dos ""' s e depois substituí-lo pela parte entre "" e envolva-o com> <'s.

amo-ej1
fonte
1

De Greg H. eu pude criar esse regex para atender às minhas necessidades.

Eu precisava corresponder a um valor específico qualificado por estar entre aspas. Deve ser uma correspondência completa, nenhuma correspondência parcial pode causar um acerto

por exemplo, "teste" não pode corresponder a "teste2".

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

Caçador

motoprog
fonte
1

Se você estiver tentando encontrar seqüências que possuem apenas um sufixo, como sintaxe de ponto, tente:

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

Onde .localizedestá o sufixo?

Exemplo:

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

Ele irá capturar "this is something I need to return".localizede "so is this".localizedmas não "but this is not".

OffensivelyBad
fonte
1

Uma resposta suplementar para o subconjunto de codificadores Microsoft VBA, apenas um usa a biblioteca Microsoft VBScript Regular Expressions 5.5e isso fornece o seguinte código

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
fonte
0

Para mim trabalhou este:

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

Eu usei em uma frase como esta:

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

e funcionou muito bem.

Alexandru Furculita
fonte
Uma fraqueza dessa abordagem é que ela corresponderá quando uma sequência começar com uma aspas simples e terminar com aspas duplas ou vice-versa.
Ghopper21
Ele também tem problemas para capturar "Não esqueça o @" - para depois de "Don".
Benny Neugebauer
0

Todas as respostas acima são boas .... exceto que NÃO suportam todos os caracteres unicode! em ECMA Script (Javascript)

Se você é um usuário do Nó, convém a versão modificada da resposta aceita que suporte todos os caracteres unicode:

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

Tente aqui .

Donovan P
fonte
1
O que é um caractere não unicode? O unicode AFAIK abrange todos os caracteres.
Toto
1
Por que você acha que é uma pergunta javascript? Além disso, o lookbehind não é suportado em todos os navegadores, lança o regex101? The preceding token is not quantifiable
Toto
@Toto, o que quero dizer é "não suporta todos os caracteres unicode". Obrigado. Embora a questão seja sobre regex em geral, não quero enfatizar que o uso de asserções de limite de palavras causaria comportamento indesejado no Javascript. E, é claro, embora os Javascripts sejam geralmente para navegador, também há Node.
Donovan P