Grep: o asterisco (*) nem sempre funciona

11

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

ThisExampleString

... para a expressão This*Stringou *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 pensei que o asterisco indicava algum número de caracteres desconhecidos? Por que isso só funciona se estiver no início da expressão? Se esse é o comportamento pretendido, o que eu uso em vez das expressões This*Stringe *String?

Trae
fonte
porque não é assim que regex trabalho ...: (em particular * != any number of unknown charactersler o doc..)
njzk2

Respostas:

18

Um asterisco em expressões regulares significa "corresponder ao elemento anterior 0 ou mais vezes".

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

No caso de grep '*String' file.txt, você está dizendo "grep, combine-me com a string vazia - literalmente nada - precedendo a palavra String". Claro, não ThisExampleStringé assim que se deve ler. (Existem 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 .significa "qualquer caractere único", que poderia fazer isso: grep 'This.*String' file.txt. Agora, o comando grep irá lê-lo corretamente: Thisseguido por qualquer caractere (pense nele como uma seleção de caracteres ASCII) repetido várias vezes, seguido por String.

Sergiy Kolodyazhnyy
fonte
6
No Bash (e na maioria dos shells do Unix), *existe um caractere especial e deve ser citado ou escapado, por exemplo, como este: grep 'This*String' file.txtou isto: grep This\*String file.txtpara não ser surpreendido por resultados inesperados.
Pabouk # 5/15
2
@pabouk em conchas, o *é um curinga. No grep, *é um operador de expressão regular. Veja unix.stackexchange.com/q/57957/70524
muru
11
pabouk está certo, a expansão do nome do arquivo ocorre antes da execução do comando; comparar strace grep .* file.txt |& head -n 1 e strace grep '.*' file.txt |& head -n 1. Também realmente grepfunciona também com qualquer caractere Unicode (por exemplo, echo -ne ⇏ | grep ⇏saídas )
kos
1
@ Berg: você tem uma grande reputação aqui, então eu pensei que você percebesse imediatamente o que eu quis dizer. O OP marcou a questão bash, então presumo que os comandos discutidos sejam interpretados por bash. Isso significa que primeiro bashinterpreta seus caracteres especiais e somente depois de todas as expansões executadas, ele passa os parâmetros para o processo gerado. ----- Por exemplo, este comando no Bash: grep This.\*String file.txtvão aparecer /bin/grepcom esses parâmetros 0: grep, 1: This.*String2: file.txt. Observe que o Bash removeu a barra invertida e o originalmente escapado *foi passado literalmente.
Pabouk # 5/15
7
O engraçado (e para solucionar problemas bastante desagradáveis ​​:) é que seus comandos grep This.*String file.txtnormalmente funcionam porque provavelmente não haverá um arquivo correspondente à expressão curinga do shell This.*String. Nesse caso, por padrão, o Bash passa o argumento literalmente incluindo *.
pabouk
8

O *metacaractere em BRE 1 s, ERE 1 se PCRE 1 s corresponde a 0 ou mais ocorrências do padrão agrupado anteriormente (se um padrão agrupado estiver precedendo o *metacaractere), 0 ou mais ocorrências da classe anterior de caracteres (se uma classe de caractere for precedendo o *metacaractere) ou 0 ou mais ocorrências do caractere anterior (se nem um padrão agrupado nem uma classe de caractere estiverem precedendo 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 a 0 ou mais ocorrências de qualquer caractere, você deseja corresponder a 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 contrário, você pode ativar grepo mecanismo PCRE (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

kos
fonte
Obrigado pela resposta muito informativa. No entanto, escolhi uma resposta diferente porque era mais curta e fácil de entender. +1 por fornecer tantos detalhes.
Trae 5/10
@ Trae De nada. Tudo bem, eu concordo que talvez isso fosse muito complexo e tenha feito muitas suposições para alguém que não esteja muito familiarizado com o tópico.
kos
4

Uma das explicações encontradas aqui link :

O asterisco " *" não significa o mesmo em expressões regulares e em caracteres curinga; é 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.

Ova
fonte
1

*tem um significado especial como um caractere globbing do shell ("curinga") e como um metacaractere de expressão regular . Você deve levar os dois em consideração, embora, se citar sua expressão regular, poderá impedir que o shell o trate especialmente e garantir que ele o passe inalterado grep. Embora tipo de semelhante conceitualmente, o que *significa que a casca é bastante diferente do que significa grep.

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

Quando o shell encontra um *caractere que não é citado , ele significa "zero ou mais de qualquer caractere" e substitui a palavra que o contém por uma lista de nomes de arquivos que correspondem ao padrão. (Os nomes de arquivos que começam com .são excluídos - a menos que seu próprio padrão comece com . ou você tenha configurado 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 grepgeralmente será que o primeiro nome de arquivo correspondente seja considerado a expressão regular - mesmo que seja óbvio para um leitor humano que ele não seja uma expressão regular - enquanto todos os outros nomes de arquivos listados automaticamente em seu glob são considerados os arquivos dentro dos quais procurar correspondências. (Você não vê a lista - ela é passada de maneira opaca grep.) Você praticamente nunca deseja que isso aconteça.

A razão pela qual isso às vezes não é um problema - e, no seu caso particular, pelo menos até agora , não foi - é que *será deixado sozinho se tudo o que se segue for verdadeiro :

  1. Não havia arquivos cujos nomes correspondessem. ... Ou você 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 em branco *quando não há nomes de arquivos correspondentes. É 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, é que os globs (a) expandam ou (b) produzam um erro.) ... Ou você alterou esse comportamento do shell - a maneira como isso é feito varia através de conchas.

  3. Caso contrário, você não disse ao seu shell para permitir que os globs sejam substituídos por nada quando não houver arquivos correspondentes, nem falhe com uma mensagem de erro nessa situação. No Bash, isso seria feito ativando a opçãonullglob ou failglob shell , respectivamente.

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

Em seguida, o grepcomando trata *como um quantificador.

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

Supondo que o *faça chegar ao grep - o que a citação deve garantir - grep, significa que o item que o precede pode ocorrer várias vezes , em vez de ter que ocorrer exatamente uma vez . Ainda pode ocorrer uma vez. Ou pode não estar presente. Ou pode ser repetido. O texto que se encaixa com qualquer uma dessas possibilidades será correspondido.

O que quero dizer com "item"?

  • Um único personagem . Desde bpartidas um literal b, b*corresponde a zero ou mais bs, portanto, ab*ccorresponde ac, abc, abbc, abbbc, etc.

    Da mesma forma, uma vez que .corresponde a qualquer caractere , .*corresponde a zero ou mais caracteres 1 , portanto, a.*cjogos ac, akc, ahjglhdfjkdlgjdfkshlgc, mesmo acccccchjckhcc, etc. Ou

  • Uma classe de personagem . Desde [xy]partidas xou y, [xy]*corresponde a zero ou mais caracteres, em que cada um é ou xou y, portanto, p[xy]*qcorresponde a pq, pxq, pyq, pxxq, pxyq, pyxq, pyyq, pxxxq, pxxyq, etc.

    Isso também se aplica a taquigrafia formas de classes de personagens como \w, \W, \s, e \S. Como \wcorresponde a qualquer caractere de palavra, \w*corresponde a zero ou mais caracteres de palavra. Ou

  • Um grupo . Desde \(bar\)partidas bar, \(bar\)*corresponde a zero ou mais bars, portanto, foo\(bar\)*bazcorresponde foobaz, foobarbaz, foobarbarbaz, foobarbarbarbaz, etc.

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

man grepfornece uma explicação razoavelmente acessível da sintaxe BRE e ERE no final, além de listar todas as opções de linha de comando grepaceitas 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 (ao qual vinculei várias páginas acima).

Para testar e aprender grep, recomendo chamá-lo com um padrão, mas sem nome de arquivo. Depois, é preciso receber informações do seu terminal. Digite linhas; as linhas que retornam para você são as que continham o texto correspondente ao seu padrão. Para sair, pressione Ctrl+ Dno início de uma linha, que sinaliza o final da entrada. (Ou você pode pressionar Ctrl+ Ccomo na maioria dos programas de linha de comando.) Por exemplo:

grep 'This.*String'

Se você usar o --colorsinalizador, grepdestacará 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ê procura depois de fazer. Por padrão, os usuários do Ubuntu têm um alias do Bash que causa grep --color=autoa execução - o que é suficiente para esse fim - quando você executa a greppartir da linha de comando, portanto, você provavelmente nem precisa passar --colormanualmente.

1 Portanto, .*em uma expressão regular significa o que *significa em uma concha glob. No entanto, a diferença é que grepimprime automaticamente as 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.

Eliah Kagan
fonte