Grep: O asterisco (*) nem sempre funciona

5

Se eu grep um documento que contém o seguinte:

ThisExampleString

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

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

Eu achava que o asterisco indicava qualquer número de caracteres desconhecidos? Por que isso só funciona se estiver no início da expressão? Se este é o comportamento pretendido, o que eu uso em vez das expressões This*String e *String ?

    
por Trae 05.10.2015 / 08:18

4 respostas

9

Um asterisco em expressões regulares significa "corresponde 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 com a palavra Thi , seguida por minúscula s zero ou mais vezes, seguida pela palavra String " . O s minúsculo não está em nenhum lugar em Example , por isso o grep ignora ThisExampleString .

No caso de grep '*String' file.txt , você está dizendo "grep, combine-me com a string vazia - literalmente nada - antes da palavra String ". Claro, não é assim que ThisExampleString deve ser lido. (Existem outros possíveis significados - você pode tentar isso com e sem o sinal -E - mas nenhum dos significados é parecido com o que você realmente quer aqui.

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

    
por Sergiy Kolodyazhnyy 05.10.2015 / 08:40
8

O metacaractere * em BRE 1 s, ERE 1 se PCRE 1 corresponde a 0 ou mais ocorrências do agrupamento anterior padrão (se um padrão agrupado estiver precedendo o caractere * ), 0 ou mais ocorrências da classe de caractere anterior (se uma classe de caracteres estiver precedendo o caractere * ) ou 0 ou mais ocorrências do caractere anterior (se não houver padrão agrupado nem uma classe de caracteres está precedendo o * metacaractere);

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

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

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

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

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

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

Isso pode não ser o comportamento desejado; caso contrário, você pode ativar o mecanismo PCRE de grep (usando a opção -P ) e anexar o caractere ? , 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

    
por kos 05.10.2015 / 08:43
4

Uma das explicações encontradas aqui link :

  

O asterisco " * " não significa a mesma coisa em expressões regulares como no curinga; é um modificador que se aplica ao caractere único precedente, 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.

    
por Ova 05.10.2015 / 08:42
1

* tem um significado especial, tanto como caractere globbing ("curinga") do shell e como uma expressão regular metacaracteres . Você deve levar os dois em consideração, embora se você citar sua expressão regular, então você pode evitar que o shell o trate especialmente e garanta que ele passa-o inalterado para grep . Embora tipo semelhante conceitualmente, o que * significa para o shell é bem diferente do que significa grep .

Primeiro o shell trata * como um caractere 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 executar o comando. Para padrões que contêm o separador de diretório / , pode depender de quais arquivos existem em todo o sistema. Você deve sempre citar expressões regulares para grep - e aspas simples geralmente são melhores - a menos que você tenha certeza de que está tudo bem com os nove tipos de transformações potencialmente surpreendentes que o shell executa antes antes executando o comando grep .

Quando o shell encontra um caractere * que não é citado , significa "zero ou mais de qualquer caractere" e substitui a palavra que o contém com 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 padrão inicie com . ou você configurou seu shell para incluí-los de qualquer maneira.) Isso é conhecido como globbing - e também pelos nomes expansão do nome do arquivo e expansão do nome do caminho .

O efeito com grep normalmente é que o primeiro nome de arquivo correspondente é tomado como a expressão regular - mesmo que seja óbvio para um leitor humano que não é expressão regular - enquanto todos os outros nomes de arquivos listados automaticamente a partir do glob são tomados como os arquivos dentro que procurar por 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 disso algumas vezes não é um problema - e no seu caso particular, pelo menos até agora , não foi - será que * será ser deixado sozinho se todos os itens a seguir forem verdadeiros :

  1. Não havia nenhum arquivos cujos nomes correspondessem. ... Ou você desativou a globulação em seu shell, normalmente com set -f ou o equivalente set -o noglob . Mas isso é incomum e você provavelmente sabe que fez isso.

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

  3. Você ainda não disse ao seu shell para permitir que globs sejam substituídos por nothing quando não há arquivos correspondentes, nem para falhar com uma mensagem de erro essa situação. No Bash, isso teria sido possível ativando o shell nullglob ou failglob opção , respectivamente.

Você pode, às vezes, confiar nos números 2 e 3, mas raramente pode confiar no número 1. Um comando grep 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ão o comando grep trata * como um quantificador.

As outras respostas - como as de Sergiy Kolodyazhnyy e by kos - também abordamos esse aspecto dessa questão de maneiras diferentes. Por isso, incentivo aqueles que ainda não os leram a fazê-lo, antes ou depois de ler o restante desta resposta.

Assumindo que o * o faça para o grep - que deve ser citado - grep então significa que o item que o precede pode ocorrer qualquer número de vezes , em vez de ter que ocorrer exatamente uma vez . Ainda pode ocorrer uma vez. Ou pode não estar presente de todo. Ou pode ser repetido. O texto que se ajusta a qualquer dessas possibilidades será correspondido.

O que quero dizer com "item"?

  • Um único caracter . Como b corresponde a um literal b , b* corresponde a zero ou mais b s, portanto ab*c corresponde a ac , abc , abbc , abbbc , etc.

    Da mesma forma, como . corresponde a qualquer caractere , .* corresponde a zero ou mais caracteres 1 , assim, a.*c corresponde a ac , akc , ahjglhdfjkdlgjdfkshlgc , mesmo acccccchjckhcc , etc. Ou

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

    Isso também se aplica a formulários abreviados de classes de caracteres como \w , \W , \s e \S . Como \w corresponde a qualquer caractere de palavra, \w* corresponde a zero ou mais caracteres de palavra. Ou

  • Um grupo . Como \(bar\) corresponde a bar , \(bar\)* corresponde a zero ou mais bar s, portanto foo\(bar\)*baz corresponde a foobaz , foobarbaz , foobarbarbaz , foobarbarbarbaz , etc.

    Com as opções -E ou -P , grep trata sua expressão regular como ERE ou PCRE respectivamente, e não como BRE e, em seguida, os grupos estão rodeados por ( ) em vez de \( \) , então você usaria (bar) em vez de \(bar\) e foo(bar)baz de foo\(bar\)baz .

man grep dá uma opinião razoável explicação acessível da sintaxe BRE e ERE no final, bem como listar todas as opções de linha de comando grep aceita no início. Eu recomendo essa página de manual como um recurso, e também a documentação do GNU Grep e este site de tutorial / referência (que eu associei 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, leva a entrada do seu terminal. Digite 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 + D no início de uma linha, que sinaliza o final da entrada. (Ou você pode pressionar Ctrl + C como na maioria dos programas de linha de comando). Por exemplo:

grep 'This.*String'

Se você usar o --color flag, grep destacará as partes específicas de suas linhas que correspondem à sua expressão regular, o que é muito útil para descobrir o que uma expressão regular faz e para encontrar o que você está procurando uma vez que você faz. Por padrão, os usuários do Ubuntu têm um alias Bash que faz com que grep --color=auto seja executado - o que é suficiente para essa finalidade - quando você executa grep na linha de comando, então você nem precisa passar --color manualmente.

1 Portanto, .* em uma expressão regular significa o que * significa em um shell glob. No entanto, a diferença é que grep imprime automaticamente linhas que contêm sua correspondência em qualquer lugar , portanto, normalmente é desnecessário ter .* no início ou no final de uma expressão regular.

    
por Eliah Kagan 20.09.2017 / 00:33