O que é um grupo que não captura em expressões regulares?

Respostas:

2329

Deixe-me tentar explicar isso com um exemplo.

Considere o seguinte texto:

http://stackoverflow.com/
/programming/tagged/regex

Agora, se eu aplicar o regex abaixo sobre ele ...

(https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

... Eu obteria o seguinte resultado:

Match "http://stackoverflow.com/"
     Group 1: "http"
     Group 2: "stackoverflow.com"
     Group 3: "/"

Match "/programming/tagged/regex"
     Group 1: "https"
     Group 2: "stackoverflow.com"
     Group 3: "/questions/tagged/regex"

Mas não me importo com o protocolo - só quero o host e o caminho da URL. Então, altero a regex para incluir o grupo que não captura (?:).

(?:https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

Agora, meu resultado fica assim:

Match "http://stackoverflow.com/"
     Group 1: "stackoverflow.com"
     Group 2: "/"

Match "/programming/tagged/regex"
     Group 1: "stackoverflow.com"
     Group 2: "/questions/tagged/regex"

Vejo? O primeiro grupo não foi capturado. O analisador usa-o para corresponder ao texto, mas o ignora mais tarde, no resultado final.


EDITAR:

Conforme solicitado, deixe-me tentar explicar os grupos também.

Bem, os grupos servem a muitos propósitos. Eles podem ajudá-lo a extrair informações exatas de uma correspondência maior (que também pode ser nomeada), permitem revidar um grupo correspondente anterior e podem ser usadas para substituições. Vamos tentar alguns exemplos, vamos?

Imagine que você tenha algum tipo de XML ou HTML (saiba que o regex pode não ser a melhor ferramenta para o trabalho , mas é bom como exemplo). Você deseja analisar as tags para poder fazer algo assim (adicionei espaços para facilitar a compreensão):

   \<(?<TAG>.+?)\> [^<]*? \</\k<TAG>\>
or
   \<(.+?)\> [^<]*? \</\1\>

O primeiro regex possui um grupo nomeado (TAG), enquanto o segundo usa um grupo comum. Ambas as expressões regulares fazem a mesma coisa: elas usam o valor do primeiro grupo (o nome da tag) para corresponder à tag de fechamento. A diferença é que o primeiro usa o nome para corresponder ao valor e o segundo usa o índice do grupo (que começa em 1).

Vamos tentar algumas substituições agora. Considere o seguinte texto:

Lorem ipsum dolor sit amet consectetuer feugiat fames malesuada pretium egestas.

Agora, vamos usar esse regex idiota sobre ele:

\b(\S)(\S)(\S)(\S*)\b

Essa expressão regular corresponde a palavras com pelo menos três caracteres e usa grupos para separar as três primeiras letras. O resultado é este:

Match "Lorem"
     Group 1: "L"
     Group 2: "o"
     Group 3: "r"
     Group 4: "em"
Match "ipsum"
     Group 1: "i"
     Group 2: "p"
     Group 3: "s"
     Group 4: "um"
...

Match "consectetuer"
     Group 1: "c"
     Group 2: "o"
     Group 3: "n"
     Group 4: "sectetuer"
...

Portanto, se aplicarmos a sequência de substituição:

$1_$3$2_$4

... sobre isso, estamos tentando usar o primeiro grupo, adicionar um sublinhado, usar o terceiro grupo, depois o segundo grupo, adicionar outro sublinhado e depois o quarto grupo. A sequência resultante seria como a abaixo.

L_ro_em i_sp_um d_lo_or s_ti_ a_em_t c_no_sectetuer f_ue_giat f_ma_es m_la_esuada p_er_tium e_eg_stas.

Você também pode usar grupos nomeados para substituições, usando ${name}.

Para brincar com regexes, recomendo http://regex101.com/ , que oferece uma boa quantidade de detalhes sobre como o regex funciona; Ele também oferece alguns mecanismos de regex para você escolher.

Ricardo Nolde
fonte
3
@ajsie: Grupos tradicionais (de captura) são mais úteis se você estiver executando uma operação de substituição nos resultados. Aqui está um exemplo em que pego
Steve Wortham
2
Não, não é o mesmo.
Ricardo Nolde
4
Também pode indicar que grupos não-captura são excepcionalmente útil quando se usa regex como delimitadores de divisão: "Alice e Bob" -split "\ s + (?: e | ou) \ s +"
Yevgeniy
7
Seria interessante explicar a diferença entre os grupos não capturadores (? :) e as declarações lookahead e lookbehind (? =,?!). Comecei a aprender sobre expressões regulares, mas, pelo que entendi, os grupos que não capturam são usados ​​para corresponder e "retornar" o que eles correspondem, mas esse "valor de retorno" não é "armazenado" para referência remota. As declarações lookahead e lookbehind, por outro lado, não são não apenas "armazenadas", elas também não fazem parte de uma correspondência, apenas afirmam que algo corresponderia, mas seu valor de "correspondência" é ignorado, se não me engano. . (Estou quase certo?) #
Christian
5
[] é um conjunto; [123] corresponde a qualquer caractere dentro do conjunto uma vez; [^ 123] corresponde a qualquer coisa NÃO dentro do aparelho uma vez; [^ / \ r \ n] + corresponde a um ou mais caracteres diferentes de /, \ r, \ n.
Ricardo Nolde
180

Você pode usar a captura de grupos para organizar e analisar uma expressão. Um grupo de não captura tem o primeiro benefício, mas não possui a sobrecarga do segundo. Você ainda pode dizer que um grupo de não captura é opcional, por exemplo.

Digamos que você queira corresponder ao texto numérico, mas alguns números podem ser escritos como 1º, 2º, 3º, 4º, ... Se você deseja capturar a parte numérica, mas não o sufixo (opcional), pode usar um grupo que não captura .

([0-9]+)(?:st|nd|rd|th)?

Isso corresponderá a números no formato 1, 2, 3 ... ou no formato 1, 2, 3, ... mas capturará apenas a parte numérica.

Bill the Lizard
fonte
3
Conciso e provavelmente a melhor explicação aqui.
NelsonGon
107

?: é usado quando você deseja agrupar uma expressão, mas não deseja salvá-la como uma parte correspondente / capturada da sequência.

Um exemplo seria algo para corresponder a um endereço IP:

/(?:\d{1,3}\.){3}\d{1,3}/

Observe que eu não me importo em salvar os três primeiros octetos, mas o (?:...)agrupamento permite que eu reduza a expressão regular sem incorrer na sobrecarga de capturar e armazenar uma correspondência.

RC.
fonte
38

Isso torna o grupo sem captura, o que significa que a substring correspondida por esse grupo não será incluída na lista de capturas. Um exemplo em ruby ​​para ilustrar a diferença:

"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]
sepp2k
fonte
Por que não podemos simplesmente usar "abc" .match (/.(.)./). Captura aqui?
PRASANNA SARAF 5/08/19
@PRASANNASARAF Você pode, é claro. O objetivo do código era mostrar que (?:)não produz uma captura, não demonstrar um exemplo útil de (?:). (?:)é útil quando você deseja agrupar uma subexpressão (por exemplo, quando você deseja aplicar quantificadores a uma subexpressão não atômica ou se deseja restringir o escopo de a |), mas não deseja capturar nada.
sepp2k
26

MOTIVAÇÃO HISTÓRICA:

A existência de grupos não capturadores pode ser explicada com o uso de parênteses.

Considere as expressões (a|b)ce a|bc, devido à prioridade da concatenação |, essas expressões representam dois idiomas diferentes ( {ac, bc}e {a, bc}respectivamente).

No entanto, os parênteses também são usados ​​como um grupo correspondente (como explicado pelas outras respostas ...).

Quando você deseja colocar parênteses, mas não capturar a subexpressão, use GRUPOS NÃO CAPTURANTES. No exemplo,(?:a|b)c

user2369060
fonte
6
Eu estava me perguntando o porquê. Na minha opinião, o "porquê" é vital para memorizar essas informações.
JMI MADISON
22

Deixe-me tentar isso com um exemplo:

Código Regex: (?:animal)(?:=)(\w+)(,)\1\2

Seqüência de pesquisa:

Linha 1 - animal=cat,dog,cat,tiger,dog

Linha 2 - animal=cat,cat,dog,dog,tiger

Linha 3 - animal=dog,dog,cat,cat,tiger

(?:animal) -> Grupo 1 não capturado

(?:=)-> Grupo 2 não capturado

(\w+)-> Grupo capturado 1

(,)-> Grupo capturado 2

\1 -> resultado do grupo 1 capturado, ou seja, na linha 1 é gato, na linha 2 é gato, na linha 3 é cachorro.

\2 -> resultado do grupo 2 capturado, isto é, vírgula (,)

Portanto, neste código, fornecemos \1e \2lembramos ou repetimos o resultado dos grupos capturados 1 e 2, respectivamente, posteriormente no código.

De acordo com a ordem do código (?:animal), o grupo 1 (?:=)deve ser o grupo 2 e continua.

mas, dando ao ?:não tornar o grupo de correspondência não capturado (que não conta no grupo correspondente, o número de agrupamento começa no primeiro grupo capturado e não o não capturado), para que a repetição do resultado do grupo de correspondência (?:animal)não pode ser chamado mais tarde no código.

Espero que isso explique o uso de grupos que não capturam.

insira a descrição da imagem aqui

Shekhar Gehlot
fonte
14

Grupos que capturam você pode usar posteriormente na regex para corresponder OU você pode usá-los na parte de substituição da regex. Criar um grupo de não captura simplesmente isenta esse grupo de ser usado por um desses motivos.

Grupos que não capturam são ótimos se você estiver tentando capturar muitas coisas diferentes e existem alguns grupos que não deseja capturar.

Essa é a razão pela qual eles existem. Enquanto você aprende sobre grupos, aprende sobre grupos atômicos , eles fazem muito! Também existem grupos de pesquisa, mas eles são um pouco mais complexos e pouco utilizados.

Exemplo de uso posterior no regex (referência anterior):

<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1> [Encontra uma tag xml (sem suporte ao ns)]

([A-Z][A-Z0-9]*) é um grupo de captura (nesse caso, é o nome da tag)

Posteriormente na regex, \1significa que ele corresponderá apenas ao mesmo texto que estava no primeiro grupo (o ([A-Z][A-Z0-9]*)grupo) (nesse caso, ele corresponderá à tag final).

Bob Fincheimer
fonte
você poderia dar um exemplo simples de como ele será usado posteriormente para corresponder ao OR?
never_had_a_name
quero dizer que você pode usar para combinar mais tarde ou você pode usá-lo na substituição. O ou em que a sentença foi apenas para mostrar-lhe há dois usos para um grupo de captura
Bob Fincheimer
9

Bem, eu sou um desenvolvedor de JavaScript e tentarei explicar seu significado referente ao JavaScript.

Considere um cenário em que você deseja combinar cat is animal quando gostaria de combinar gato e animal e ambos devem ter um isentre eles.

 // this will ignore "is" as that's is what we want
"cat is animal".match(/(cat)(?: is )(animal)/) ;
result ["cat is animal", "cat", "animal"]

 // using lookahead pattern it will match only "cat" we can
 // use lookahead but the problem is we can not give anything
 // at the back of lookahead pattern
"cat is animal".match(/cat(?= is animal)/) ;
result ["cat"]

 //so I gave another grouping parenthesis for animal
 // in lookahead pattern to match animal as well
"cat is animal".match(/(cat)(?= is (animal))/) ;
result ["cat", "cat", "animal"]

 // we got extra cat in above example so removing another grouping
"cat is animal".match(/cat(?= is (animal))/) ;
result ["cat", "animal"]
Gaurav
fonte
7

Em expressões regulares complexas, pode surgir a situação em que você deseja usar um grande número de grupos, alguns dos quais existem para correspondência de repetições e outros para fornecer referências anteriores. Por padrão, o texto correspondente a cada grupo é carregado na matriz de referência anterior. Onde temos muitos grupos e precisamos apenas fazer referência a alguns deles da matriz de referência anterior, podemos substituir esse comportamento padrão para dizer à expressão regular que certos grupos estão lá apenas para manipulação de repetição e não precisam ser capturados e armazenados na matriz de referência anterior.

Jack Peng
fonte
7

Não posso comentar nas respostas principais para dizer o seguinte: gostaria de adicionar um ponto explícito, que está implícito apenas nas respostas principais:

O grupo (?...) que não captura não remove nenhum caractere da correspondência completa original, apenas reorganiza a regex visualmente para o programador.

Para acessar uma parte específica da regex sem caracteres estranhos definidos, você sempre precisará usar .group(<index>)

Scott Anderson
fonte
2
Você forneceu a dica mais importante que faltava no restante das respostas. Tentei todos os exemplos e usei os palavrões mais seletivos, pois não obtive o resultado desejado. Somente sua postagem me mostrou onde eu errei.
Seshadri R
Fico feliz em ouvir isso!
21818 Scott Anderson
6

tl; dr grupos que não capturam, como o nome sugere, são as partes da regex que você não deseja incluir na correspondência e ?:é uma maneira de definir um grupo como não capturando.

Digamos que você tenha um endereço de e-mail [email protected]. O regex a seguir criará dois grupos , a parte id e a parte @ example.com. (\p{Alpha}*[a-z])(@example.com). Por uma questão de simplicidade, estamos extraindo todo o nome de domínio, incluindo o @personagem.

Agora, digamos, você só precisa da parte id do endereço. O que você quer fazer é pegar o primeiro grupo do resultado da partida, cercado pela ()regex e a maneira de fazer isso é usar a sintaxe do grupo que não captura ?:. Portanto, a regex (\p{Alpha}*[a-z])(?:@example.com)retornará apenas a parte de identificação do email.

6pack kid
fonte
5

Uma coisa interessante que me deparei é o fato de que você pode ter um grupo de captura dentro de um grupo não-captura. Confira abaixo a regex para correspondência de URLs da Web:

var parse_url_regex = /^(?:([A-Za-z]+):)(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

String de URL de entrada:

var url = "http://www.ora.com:80/goodparts?q#fragment";

O primeiro grupo no meu regex (?:([A-Za-z]+):)é um grupo não capturável que corresponde ao esquema de protocolo e ao :caractere de dois pontos , ou seja, http:mas quando eu estava executando abaixo do código, estava vendo que o primeiro índice da matriz retornada continha a string httpquando eu pensava nisso httpe dois pontos :ambos não serão relatados, pois estão dentro de um grupo que não captura.

console.debug(parse_url_regex.exec(url));

insira a descrição da imagem aqui

Eu pensei que se o primeiro grupo (?:([A-Za-z]+):)é um grupo não-captura, então por que está retornandohttp seqüência na matriz de saída.

Portanto, se você perceber que há um grupo aninhado ([A-Za-z]+)dentro do grupo que não captura. Esse grupo aninhado ([A-Za-z]+)é um grupo de captura (que não existe ?:no início) em si mesmo dentro de um grupo de não captura (?:([A-Za-z]+):). É por isso que o texto httpainda é capturado, mas o :caractere de dois pontos que está dentro do grupo de não captura, mas fora do grupo de captura, não é relatado na matriz de saída.

RBT
fonte
2

Abra seu devTools do Google Chrome e, em seguida, guia Console: e digite isto:

"Peace".match(/(\w)(\w)(\w)/)

Execute-o e você verá:

["Pea", "P", "e", "a", index: 0, input: "Peace", groups: undefined]

O JavaScriptmecanismo RegExp captura três grupos, os itens com índices 1,2,3. Agora use a marca de não captura para ver o resultado.

"Peace".match(/(?:\w)(\w)(\w)/)

O resultado é:

["Pea", "e", "a", index: 0, input: "Peace", groups: undefined]

É óbvio o que não é um grupo de captura.

AmerllicA
fonte
2

Eu acho que daria a resposta. Não use variáveis ​​de captura sem verificar se a correspondência foi bem-sucedida.

As variáveis ​​de captura $1, etc, não são válidas, a menos que a correspondência tenha sido bem-sucedida e também não são limpas.

#!/usr/bin/perl  
use warnings;
use strict;   
$_ = "bronto saurus burger";
if (/(?:bronto)? saurus (steak|burger)/)
{
    print "Fred wants a  $1";
}
else
{
    print "Fred dont wants a $1 $2";
}

No exemplo acima, para evitar capturar bronto in $1, (?:)é usado.

Se o padrão for correspondido, $1será capturado como o próximo padrão agrupado.

Portanto, a saída será a seguinte:

Fred wants a burger

É útil se você não quiser que as correspondências sejam salvas.

Harini
fonte
1

É extremamente simples, podemos entender com um exemplo simples de data, suponha que a data seja mencionada como 1º de janeiro de 2019 ou 2 de maio de 2019 ou qualquer outra data e queremos simplesmente convertê-la em dd / mm / aaaa , não precisaremos do mês nome que é janeiro ou fevereiro para esse assunto, portanto, para capturar a parte numérica, mas não o sufixo (opcional), você pode usar um grupo que não captura.

então a expressão regular seria,

([0-9]+)(?:January|February)?

É simples assim.

Naved Ahmad
fonte