Grep: O asterisco (*) nem sempre funciona

Grep: O asterisco (*) nem sempre funciona

Se eu usar grep em um documento que contenha o seguinte:

ThisExampleString

...para a expressão This*Stringor *String, nada é retornado. No entanto, This*retorna a linha acima conforme esperado.

Se a expressão está entre aspas não faz diferença.

Achei que o asterisco indicava algum número de caracteres desconhecidos? Por que só funciona se estiver no início da expressão? Se este for o comportamento pretendido, o que devo usar em vez das expressões This*Stringe *String?

Responder1

Um asterisco emexpressões regularessignifica "corresponder ao elemento anterior 0 ou mais vezes".

No seu caso particular com grep 'This*String' file.txt, você está tentando dizer "ei, grep, combine-me a palavra Thi, seguida de szero ou mais letras minúsculas, seguida da palavra String". As letras minúsculas snão podem ser encontradas em nenhum lugar Example, portanto, grep ignora ThisExampleString.

No caso de grep '*String' file.txt, você está dizendo "grep, combine-me com a string vazia - literalmente nada - que precede a palavra String". Claro, não é assim ThisExampleStringque se deve ler. (Háoutros significados possíveis--você pode tentar isso com e sem a -Ebandeira--mas nenhum dos significados é parecido com o que você realmente deseja aqui.)

Sabendo que isso .significa "qualquer caractere único", poderíamos fazer o seguinte: grep 'This.*String' file.txt. Agora o comando grep irá lê-lo corretamente: Thisseguido por qualquer caractere (pense nisso como uma seleção de caracteres ASCII) repetido qualquer número de vezes, seguido por String.

Responder2

O *metacaractere em BRE 1 s, ERE 1 s e PCRE 1 s corresponde a 0 ou mais ocorrências do padrão agrupado anteriormente (se um padrão agrupado precede o *metacaractere), 0 ou mais ocorrências da classe de caracteres anterior (se uma classe de caracteres for precedendo o *metacaractere) ou 0 ou mais ocorrências do caractere anterior (se nem um padrão agrupado nem uma classe de caracteres precedem o *metacaractere);

Isso significa que no This*Stringpadrão, sendo o *metacaractere não precedido por um padrão agrupado ou por uma classe de caracteres, o *metacaractere corresponde a 0 ou mais ocorrências do caractere anterior (neste caso o scaractere):

% cat infile               
ThisExampleString
ThisString
ThissString
% grep 'This*String' infile
ThisString
ThissString

Para corresponder 0 ou mais ocorrências de qualquer caractere, você deseja corresponder 0 ou mais ocorrências do .metacaractere, que corresponde a qualquer caractere:

% cat infile               
ThisExampleString
% grep 'This.*String' infile
ThisExampleString

O *metacaractere em BREs e EREs é sempre "ganancioso", ou seja, corresponderá à correspondência mais longa:

% cat infile
ThisExampleStringIsAString
% grep -o 'This.*String' infile
ThisExampleStringIsAString

Este pode não ser o comportamento desejado; caso não esteja, você pode ligar grepo mecanismo PCRE de (usando a -Popção) e anexar o ?metacaractere, que quando colocado após os *metacaracteres +e tem o efeito de alterar sua ganância:

% cat infile
ThisExampleStringIsAString
% grep -Po 'This.*?String' infile
ThisExampleString

1: Expressões regulares básicas, expressões regulares estendidas e expressões regulares compatíveis com Perl

Responder3

Uma das explicações encontradas aquilink:

O asterisco " *" não significa a mesma coisa em expressões regulares e em curingas; é um modificador que se aplica ao caractere único anterior ou expressão como [0-9]. Um asterisco corresponde a zero ou mais do que o precede. Assim, [A-Z]*corresponde a qualquer número de letras maiúsculas, incluindo nenhuma, enquanto [A-Z][A-Z]*corresponde a uma ou mais letras maiúsculas.

Responder4

*tem um significado especial tanto como conchaglobulandocaractere ("curinga") e como expressão regularmetacaractere. Você deve levar ambos em consideração, embora se vocêcitarsua expressão regular, então você pode evitar que o shell a trate de maneira especial e garantir que ela seja transmitida inalterada paragrep. Emboratipo desemelhante conceitualmente, o que *significa para o shell é bem diferente do que significa para grep.

Primeiroo shell trata *como um curinga.

Você disse:

Se a expressão está entre aspas não faz diferença.

Isso depende de quais arquivos existem em qualquer diretório em que você esteja quando executa o comando. Para padrões que contêm o separador de diretórios /, isso pode depender de quais arquivos existem em todo o sistema. Você deveria semprecitarexpressões regulares para grep--easpas simplesgeralmente são os melhores -a menos quevocê tem certeza de que está bem com onove tipos de transformações potencialmente surpreendenteso shell executa de outra formaantesexecutando o grepcomando.

Quando o shell encontra um *personagem que não écitado, significa "zero ou mais de qualquer caractere" esubstitui a palavra que o contémcom uma lista de nomes de arquivos que correspondem ao padrão. (Nomes de arquivos que começam com .são excluídos - a menos que seu próprio padrão comece com. ouvocê configurou seu shell para incluí-los de qualquer maneira.) Isso é conhecido comoglobulando--e também pelos nomesexpansão do nome do arquivoeexpansão do nome do caminho.

O efeito grepgeralmente será que o primeiro nome de arquivo correspondente seja considerado como a expressão regular - mesmo que seja bastante óbvio para um leitor humano que énãosignifica uma expressão regular - enquanto todos os outros nomes de arquivos listados automaticamente em seu globo são considerados os arquivosdentroonde procurar correspondências. (Você não vê a lista - ela é passada de forma opaca para grep.) Você praticamente nunca quer que isso aconteça.

A razão pela qual isso éàs vezesnão é um problema - e no seu caso particular, pelo menosaté aqui, não foi - é que *será deixado em pazse todas as afirmações a seguir forem verdadeiras:

  1. Havianãoarquivos cujos nomes correspondiam. ...Ouvocê desativou o globbing em seu shell, normalmente com set -fou equivalente set -o noglob. Mas isso é incomum e você provavelmente saberia que fez isso.

  2. Você está usando um shell cujo comportamento padrão é deixar *de lado quando não há nomes de arquivos correspondentes. Este é o caso do Bash, que você estáprovavelmenteusando, mas não em todos os shells do estilo Bourne. (O comportamento padrão no popular shell Zsh, por exemplo, é que os globs(a)expandir ou(b)produzir um erro.)...Ouvocê alterou esse comportamento do seu shell - a forma como isso é feito varia entre os shells.

  3. Você não temde outra formadisse ao seu shell para permitir que globs fossem substituídos pornadaquando não há arquivos correspondentes, nem falhar com uma mensagem de erro nesta situação. No Bash, isso teria sido feito habilitando o nullgloboufailglob opção de shell, respectivamente.

Às vezes você pode confiar nos números 2 e 3, mas raramente pode confiar no número 1. Um grepcomando com um padrão sem aspas que funciona agora pode parar de funcionar quando você tiver arquivos diferentes ou quando for executado em um local diferente.Cite sua expressão regular e o problema desaparece.

Entãoo grepcomando trata *como um quantificador.

As outras respostas - como aquelaspor Sergiy Kolodyazhnyyepor Kos--também abordam este aspecto desta questão, de maneiras um pouco diferentes. Portanto, encorajo aqueles que ainda não os leram a fazê-lo, antes ou depois de ler o restante desta resposta.

Supondo que *chegue ao grep - o que a citação deve garantir - grepentão isso significa queo item que o precedepode ocorrer inúmeras vezes, em vez de ter que ocorrer exatamente uma vez. Ainda pode ocorrer uma vez. Ou pode não estar presente. Ou poderia ser repetido. Texto que combina comqualquerdessas possibilidades serão correspondidas.

O que quero dizer com "item"?

  • Um únicopersonagem. Como bcorresponde a um literal b, b*corresponde a zero ou mais bs, ab*ccorrespondendo , portanto ac, a abc,,,, etc.abbcabbbc

    Da mesma forma, desde.corresponde a qualquer caractere, .*corresponde a zero ou mais caracteres1, portanto a.*ccorresponde a ac, akc, ahjglhdfjkdlgjdfkshlgc, par acccccchjckhcc, etc.Ou

  • Aclasse de personagem. Como [xy]corresponde a xou y, [xy]*corresponde a zero ou mais caracteres onde cada um é xou y, portanto p[xy]*qcorresponde a pq, pxq, pyq, pxxq, pxyq, pyxq, pyyq, pxxxq, pxxyq, etc.

    Isto também se aplica aformas abreviadasde classes de caracteres como \w, \W, \se \S. Como \wcorresponde a qualquer caractere de palavra, \w*corresponde a zero ou mais caracteres de palavra.Ou

  • Agrupo. Como \(bar\)corresponde a bar, \(bar\)*corresponde a zero ou mais bars, portanto foo\(bar\)*bazcorresponde a foobaz, foobarbaz, foobarbarbaz, foobarbarbarbaz, etc.

    Com as opções -Eou -P, greptrata sua expressão regular como umaANTESouPCRErespectivamente, e não como umBRE, e os grupos são cercados por ( )em vez de \( \), então você usaria (bar)em vez de \(bar\)e foo(bar)bazem vez de foo\(bar\)baz.

man grepfornece uma explicação razoavelmente acessível da sintaxe BRE e ERE no final, bem como lista todas as opções de linha de comando grepaceitas no início. Eu recomendo essa página de manual como recurso, e tambéma documentação do GNU Grepeeste tutorial/site de referência(que vinculei a várias páginas acima).

Para testar e aprender grep, recomendo chamá-lo com um padrão, mas sem nome de arquivo. Em seguida, ele recebe informações do seu terminal. Insira linhas; as linhas que são ecoadas de volta para você são aquelas que continham o texto correspondente ao seu padrão. Para sair, pressione Ctrl+ Dno início de uma linha, que sinaliza o fim da entrada. (Ou você pode pressionar Ctrl+ Ccomo acontece com a maioria dos programas de linha de comando.) Por exemplo:

grep 'This.*String'

Se você usar a --colorbandeira, grepirá destacar o específicopeçasde suas linhas que correspondem à sua expressão regular, o que é muito útil tanto para descobrir o que uma expressão regular faz quanto para encontrar o que você está procurando depois de fazer isso. Por padrão, os usuários do Ubuntu têm um alias Bash que faz com grep --color=autoque seja executado - o que é suficiente para esse propósito - quando você executa grepa partir da linha de comando, então provavelmente nem precisará passar --colormanualmente.

1 Portanto, .*em uma expressão regular significa o que *significa em um shell glob. Porém, a diferença é que grepimprime automaticamente as linhas que contêm sua correspondênciaem qualquer lugarneles, por isso normalmente é desnecessário ter .*no início ou no final de uma expressão regular.

informação relacionada