Como ou por que usar `. *?` É melhor que `. *`?

9

Eu respondi a essa pergunta no SuperUser que era algo relacionado ao tipo de expressões regulares usadas ao grepping uma saída.

A resposta que dei foi esta:

 tail -f log | grep "some_string.*some_string"

E então, em três comentários à minha resposta, o @Bob escreveu isso:

.*é ganancioso e pode capturar mais do que você deseja. .*?geralmente é melhor.

Então isso,

o ?é um modificador ativado *, tornando-o preguiçoso em vez do padrão ganancioso. Supondo PCRE.

Pesquisei no Google PCRE, mas não consegui entender qual é o significado disso na minha resposta?

e finalmente isso,

Devo também salientar que isso é regex (grep executando o POSIX regex por padrão), não um shell glob.

Eu só sei o que é um Regex e o uso muito básico dele no comando grep. Portanto, não consegui receber nenhum desses três comentários e tenho estas perguntas em mente:

  • Quais são as diferenças no uso de .*?contra .*?
  • Qual é o melhor e sob que circunstância? Por favor, forneça exemplos.

Também seria útil entender os comentários, se alguém puder


ATUALIZAÇÃO: Como resposta à pergunta Como o Regex difere do Shell Globs? @Kusalananda forneceu este link em seu comentário.

NOTA: Se necessário, leia minha resposta a esta pergunta antes de responder por se referir ao contexto.

C0deDaedalus
fonte
Essas são duas perguntas muito diferentes. A primeira pergunta é respondida por unix.stackexchange.com/questions/57957/… enquanto a segunda pergunta depende da aplicação do padrão (não pode ser considerado "melhor" em todas as circunstâncias).
Kusalananda
Você pode editar esta pergunta para tratar apenas da questão .*vs. .*?A questão "diferença entre expressões regulares e shell globs" já foi abordada neste site.
Kusalananda

Respostas:

7

Ashok já apontou a diferença entre .*e .*?, portanto, fornecerei algumas informações adicionais.

grep (assumindo a versão GNU) suporta 4 maneiras de corresponder as strings:

  • Strings fixas
  • Expressões regulares básicas (BRE)
  • Expressões regulares estendidas (ERE)
  • Expressões regulares compatíveis com Perl (PCRE)

grep usa BRE por padrão.

BRE e ERE estão documentados no capítulo Expressões regulares do POSIX e o PCRE está documentado em seu site oficial . Observe que os recursos e a sintaxe podem variar entre as implementações.

Vale dizer que nem BRE nem ERE apóiam a preguiça :

O comportamento de vários símbolos de duplicação adjacentes ('+', '*', '?' E intervalos) produz resultados indefinidos.

Portanto, se você quiser usar esse recurso, precisará usar o PCRE:

# BRE greedy
$ grep -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# BRE lazy
$ grep -o 'c.*\?s' <<< 'can cats eat plants?'
can cats eat plants

# ERE greedy
$ grep -E -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# ERE lazy
$ grep -E -o 'c.*?s' <<< 'can cats eat plants?'
can cats eat plants

# PCRE greedy
$ grep -P -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# PCRE lazy
$ grep -P -o 'c.*?s' <<< 'can cats eat plants?'
can cats

Editar 1

Poderia explicar um pouco sobre .*vs .*??

  • .*é usado para corresponder ao padrão "mais longo" 1 possível.

  • .*?é usado para corresponder ao padrão "mais curto" 1 possível.

Na minha experiência, o comportamento mais procurado é geralmente o segundo.

Por exemplo, digamos que temos a seguinte sequência e queremos corresponder apenas às tags html 2 , e não ao conteúdo entre elas:

<title>My webpage title</title>

Agora compare .*vs .*?:

# Greedy
$ grep -P -o '<.*>' <<< '<title>My webpage title</title>'
<title>My webpage title</title>

# Lazy
$ grep -P -o '<.*?>' <<< '<title>My webpage title</title>'
<title>
</title>

1. O significado de "mais longo" e "mais curto" em um contexto de regex é um pouco complicado, como Kusalananda apontou . Consulte a documentação oficial para obter mais informações.
2. Não é recomendado analisar html com regex . Este é apenas um exemplo para fins educacionais, não o use na produção.

nxnev
fonte
Poderia explicar um pouco sobre .*vs .*??
C0deDaedalus 5/05
@ C0deDaedalus Atualizado.
Nxnev 5/05
9

Suponha que eu pegue uma string como:

can cats eat plants?

O uso do ganancioso c.*scorresponderá a toda a cadeia, pois começa com ce termina com s, sendo um operador ganancioso que continua até a ocorrência final de s.

Enquanto o uso do preguiçoso c.*?scorresponderá apenas até que a primeira ocorrência de sseja encontrada, ou seja, string can cats.

No exemplo acima, você pode conseguir isso:

"Ganancioso" significa corresponder à corda mais longa possível. "Preguiçoso" significa corresponder à corda mais curta possível. Adicionando um ?a um quantificador como *, +, ?, ou {n,m}torna preguiçosos.

Ashok
fonte
1
O "menor possível" seria cats, portanto, não está aplicando o "menor possível" estritamente nesse sentido.
Kusalananda
2
@ Kusalananda é verdade, não estritamente nesse sentido, mas "o mais curto possível" aqui significa entre a primeira ocorrência de c e s.
Ashok
1

Uma string pode ser correspondida de várias maneiras (do mais simples ao mais complexo):

  1. Como uma sequência estática (Assuma var = 'Hello World!'):

    [ "$var" = "Hello World!" ] && echo yes
    echo "$var" | grep -F "Hello"
    grep -F "Hello" <<<"$var"

  2. Como um globo:

    echo ./* # lista todos os arquivos em pwd.
    case $var in (*Worl*) echo yes;; (*) echo no;; esac
    [[ "$var" == *"Worl"* ]] && echo yes

    Existem globs básicos e estendidos. O caseexemplo usa globs básicos. O [[exemplo do bash usa globs estendidos. A primeira correspondência de arquivo pode ser básica ou estendida em alguns shell, como a configuração extglobno bash. Ambos são idênticos neste caso. O Grep não pode usar globs.

    O asterisco em uma glob significa algo diferente de um asterisco em uma regex :

    * matches any number (including none) ofquaisquer caracteres .
    * matches any number (including none) of theelemento anterior .

  3. Como expressão regular básica (BRE):

    echo "$var" | sed 's/W.*d//' # print: Olá!
    grep -o 'W.*d' <<<"$var" # impressão Mundo!

    Não há BRE em conchas (básicas) ou awk.

  4. Expressões regulares estendidas (ERE):

    [[ "$var" =~ (H.*l) ]] # jogo: Olá Worl
    echo "$var" | sed -E 's/(d|o)//g' # impressão: Hell WRL!
    awk '/W.*d/{print $1}' <<<"$var" # print: Olá
    grep -oE 'H.*l' <<<"$var" # impressão: Olá Worl

  5. Expressões regulares compatíveis com Perl:

    grep -oP 'H.*?l # print: Hel

Somente em um PCRE a *?tem algum significado específico de sintaxe.
Torna o asterisco preguiçoso (sem graça): preguiça em vez de ganância .

$ grep -oP 'e.*l' <<<"$var"
ello Worl

$ grep -oP 'e.*?l' <<<"$var"
el

Esta é apenas a ponta do iceberg, há gananciosos, preguiçosos , dóceis ou possessivos . Também existem lookahead e lookbehind, mas eles não se aplicam ao asterisco *.

Existe uma alternativa para obter o mesmo efeito que uma regex não gananciosa:

$ grep -o 'e[^o]*o' <<<"$var"
ello

A ideia é muito simples: não use um ponto ., negue o próximo caractere a combinar [^o]. Com uma tag da web:

$ grep -o '<[^>]*>' <<<'<script type="text/javascript">document.write(5 + 6);</script>'
<script type="text/javascript">
</script>

O texto acima deve esclarecer completamente todos os comentários do @Bob 3. Parafraseando:

  • A. * É um regex comum, não um glob.
  • Somente um regex poderia ser compatível com PCRE.
  • No PCRE: a? modifique o * quantificador. .*é ganancioso .*?não é.

Questões

  • Quais são as diferenças no uso de. ? vs. ?

    • A .*?é válido apenas na sintaxe PCRE.
    • A .*é mais portátil.
    • O mesmo efeito de uma correspondência não gananciosa pode ser feito substituindo o ponto por um intervalo de caracteres negado: [^a]*
  • Qual é o melhor e sob que circunstância? Por favor, forneça exemplos.
    Melhor? Depende do objetivo. Não há melhor, cada um é útil para diferentes propósitos. Eu forneci vários exemplos acima. Precisa de mais?

Isaac
fonte