Como ou Por que usar '. *?' é melhor que '.*'?

8

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

A resposta que dei foi esta:

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

E depois, em três comentários à minha resposta @Bob escreveu isto:

.* is greedy and might capture more than you want. .*? is usually better.

Então isso,

the ? is a modifier on *, making it lazy instead of the greedy default. Assuming PCRE.

Eu pesquisei PCRE , mas não consegui saber qual é o significado disso na minha resposta?

e finalmente isso,

I should also point out that this is regex (grep doing POSIX regex by default), not a shell glob.

Eu só sei o que é um Regex e uso muito básico dele no comando grep. Então, eu não consegui nenhum desses 3 comentários e tenho estas questões em mente:

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

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

ATUALIZAÇÃO: Como resposta à pergunta Como a Regex é diferente da Shell Globs? @Kusalananda desde este link em seu comentário.

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

    
por C0deDaedalus 05.05.2018 / 08:50

3 respostas

6

Ashok já apontou a diferença entre .* e .*? , então só vou fornecer alguma informação adicional.

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

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

grep usa o BRE por padrão.

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

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

The behavior of multiple adjacent duplication symbols ( '+', '*', '?', and intervals) produces undefined results.

Então, 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

Could you please explain a little about .* 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 string e queremos corresponder apenas as tags html 2 , não o 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 expressão regular é um pouco complicado, como Kusalananda apontou . Consulte a documentação oficial para mais informações.
2. Não é recomendado analisar HTML com regex . Este é apenas um exemplo para fins educacionais, não use na produção.

    
por 05.05.2018 / 17:32
8

Suponha que eu pegue uma string como:

can cats eat plants?

Usar o greedy c.*s corresponderá a toda a string, uma vez que começa com c e termina com s , sendo um operador ganancioso que continua a corresponder até a ocorrência final de s.

Considerando que o uso do preguiçoso c.*?s só coincidirá até a primeira ocorrência de s ser encontrada, ou seja, a string can cats .

No exemplo acima, você pode conseguir isso:

"Greedy" significa corresponder à string mais longa possível. "Preguiçoso" significa corresponder à string mais curta possível. Adicionar um ? a um quantificador como * , + , ? ou {n,m} torna isso lento.

    
por 05.05.2018 / 11:03
1

Uma string pode ser combinada de várias maneiras (de simples a mais complexa):

  1. Como uma string estática (Assume var = 'Hello World!'):

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

  2. Como um glob:

    echo ./* # list < Arquivos strong> all em pwd. case $var in (*Worl*) echo yes;; (*) echo no;; esac [[ "$var" == *"Worl"* ]] && echo yes

    Existem globs básicos e estendidos. O exemplo case usa globs básicos. O bash [[ example usa globs estendidos. A primeira correspondência de arquivo pode ser básica ou estendida em algum shell, como a configuração de extglob no bash. Ambos são idênticos neste caso. Grep não pôde usar globs.

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

    * matches any number (including none) of quaisquer caracteres . * matches any number (including none) of the elemento anterior .

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

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

    Não há BRE no shell (básico) ou awk.

  4. Expressões regulares estendidas (ERE):

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

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

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

Somente em um PCRE, um *? tem algum significado específico de sintaxe.
Isso torna o asterisco preguiçoso (ungreedy): 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á ganancioso, preguiçoso e docile ou possesive . Também há lookahead e lookbehind , mas elas não se aplicam ao asterisco * .

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

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

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

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

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

  • A. * é uma regex comum, não uma glob.
  • Apenas um regex pode ser compatível com PCRE.
  • No PCRE: um? modifique o quantificador *. .* é ganancioso .*? não é.

Perguntas

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

    • Um .*? é válido apenas na sintaxe PCRE.
    • Um .* é mais portátil.
    • O mesmo efeito que uma correspondência não-gananciosa poderia ser feito substituindo o ponto por um intervalo de caracteres negados: [^a]*
  • Qual é melhor e em que circunstâncias? 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. Você precisa de mais?

por 06.05.2018 / 05:02