Como grep para grupos de n dígitos, mas não mais que n?

33

Estou aprendendo Linux e tenho um desafio que parece não conseguir resolver sozinho. Aqui está:

grep uma linha de um arquivo que contém 4 números seguidos, mas não mais que 4.

Não tenho certeza de como abordar isso. Posso procurar números específicos, mas não o valor em uma sequência.

Buda
fonte
2
Uma linha como deve 1234a12345ser exibida ou não?
Eliah Kagan
@ Buda, você precisa explicar sua pergunta junto com um exemplo.
precisa
se os números forem precedidos por espaço ou início da âncora de linha e seguidos por um espaço ou final da âncora de linha, você poderá simplesmente usar limites de palavras. \b\d{4}\b
precisa
1
Esta pergunta difere de algumas perguntas sobre expressões regulares por ser explicitamente sobre o uso do grep . Perguntas sobre o uso de utilitários Unix no Ubuntu, como grep, sed e awk, sempre foram consideradas boas aqui. Às vezes, as pessoas perguntam como fazer um trabalho com a ferramenta errada ; então a falta de contexto é um grande problema, mas não é isso que está acontecendo aqui. Isso é tópico, claro o suficiente para ser útil para responder, útil para a nossa comunidade e não há benefício em impedir respostas adicionais ou empurrá-lo para a exclusão ou migração. Estou votando para reabri-lo.
Eliah Kagan
1
Muito obrigado a vocês, eu não tinha idéia de receber tanto feedback. Esta é a resposta que eu estava procurando: arquivo grep -E '(^ ​​| [^ 0-9]) [0-9] {4} ($ | [^ 0-9])'. O comando deve ser capaz de puxar uma seqüência de caracteres como esta (o que ocorre): abc1234abcd99999
Buddha

Respostas:

52

Existem duas maneiras de interpretar essa pergunta; Vou abordar os dois casos. Você pode querer exibir linhas:

  1. que contêm uma sequência de quatro dígitos que não faz parte de nenhuma sequência mais longa de dígitos, ou
  2. que contém uma sequência de quatro dígitos, mas não mais uma sequência de dígitos (nem mesmo separadamente).

Por exemplo, (1) seria exibido 1234a56789, mas (2) não.


Se você deseja exibir todas as linhas que contêm uma sequência de quatro dígitos que não faz parte de nenhuma sequência mais longa de dígitos, uma maneira é:

grep -P '(?<!\d)\d{4}(?!\d)' file

Isso usa expressões regulares do Perl , suportadas pelo Ubuntu grep( GNU grep ) -P. Não corresponderá a texto como 12345, nem corresponderá ao 1234ou 2345que faz parte dele. Mas vai combinar com o 1234in 1234a56789.

Nas expressões regulares do Perl:

  • \dsignifica qualquer dígito (é uma maneira curta de dizer [0-9]ou [[:digit:]]).
  • x{4}corresponde x4 vezes. (A { }sintaxe não é específica para expressões regulares do Perl; também está em expressões regulares estendidas grep -E.) Assim \d{4}é o mesmo que \d\d\d\d.
  • (?<!\d)é uma asserção negativa de largura zero. Significa "a menos que seja precedido por \d".
  • (?!\d)é uma afirmação de antecipação negativa de largura zero. Significa "a menos que seja seguido por \d".

(?<!\d)e (?!\d)não corresponde ao texto fora da sequência de quatro dígitos; em vez disso, eles (quando usados ​​juntos) impedirão que uma sequência de quatro dígitos seja correspondida se fizer parte de uma sequência mais longa de dígitos.

Usar apenas o look-behind ou apenas o look-ahead é insuficiente porque a subsequência de quatro dígitos mais à direita ou à esquerda ainda seria correspondida.

Um benefício do uso de asserções look-behind e look-ahead é que seu padrão corresponde apenas às seqüências de quatro dígitos, e não ao texto ao redor. Isso é útil ao usar o destaque de cores (com a --coloropção).

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

Por padrão no Ubuntu, cada usuário tem alias grep='grep --color=auto'em seu ~.bashrcarquivo . Assim, você obtém o realce de cores automaticamente quando executa um comando simples começando com grep(isto é, quando os aliases são expandidos) e a saída padrão é um terminal (é isso que verifica). As correspondências geralmente são destacadas em um tom de vermelho (próximo ao vermelhão ), mas eu o mostrei em negrito em itálico. Aqui está uma captura de tela:--color=auto
Captura de tela mostrando o comando grep, com 12345abc789d0123e4 como saída, com o 0123 destacado em vermelho.

E você pode até grepimprimir apenas o texto correspondente, e não a linha inteira, com -o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

Maneira Alternativa, Sem Declarações de Look-Behind e Look-Ahead

No entanto, se você:

  1. precisa de um comando que também seja executado em sistemas onde grepnão suporta -Pou de outra forma não deseja usar uma expressão regular Perl, e
  2. não precisa corresponder especificamente aos quatro dígitos - o que geralmente acontece se seu objetivo é simplesmente exibir linhas contendo correspondências e
  3. estão bem com uma solução um pouco menos elegante

... então você pode conseguir isso com uma expressão regular estendida :

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Combina quatro dígitos e o caractere que não é um dígito - ou o início ou o fim da linha - ao redor deles. Especificamente:

  • [0-9]corresponde a qualquer dígito (como [[:digit:]], ou \dem Perl expressões regulares) e {4}significa "quatro vezes." Então [0-9]{4}corresponde a uma sequência de quatro dígitos.
  • [^0-9]corresponde caracteres não na faixa de 0meio 9. É equivalente a [^[:digit:]](ou \D, em expressões regulares do Perl).
  • ^, quando não aparece [ ]entre colchetes, corresponde ao início de uma linha. Da mesma forma, $corresponde ao final de uma linha.
  • |meios ou e parênteses são para agrupar (como na álgebra). Portanto, (^|[^0-9])corresponde ao início da linha ou a um caractere que não é dígito, enquanto ($|[^0-9])corresponde ao final da linha ou a um caractere que não é dígito.

Portanto, as correspondências ocorrem apenas em linhas que contêm uma sequência de quatro dígitos ( [0-9]{4}) que é simultaneamente:

  • no início da linha ou precedido por um não dígito ( (^|[^0-9])), e
  • no final da linha ou seguido por um não dígito ( ($|[^0-9])).

Se, por outro lado, você deseja exibir todas as linhas que contêm uma sequência de quatro dígitos, mas não contém nenhuma sequência com mais de quatro dígitos (mesmo uma que esteja separada de outra sequência de apenas quatro dígitos), então conceitualmente O objetivo é encontrar linhas que correspondam a um padrão, mas não a outro.

Portanto, mesmo que você saiba como fazê-lo com um único padrão, sugiro usar algo como a segunda sugestão de matt , greppara os dois padrões separadamente.

Você não se beneficia fortemente de nenhum dos recursos avançados das expressões regulares do Perl ao fazer isso; portanto, pode preferir não usá-los. Mas, de acordo com o estilo acima, aqui está uma redução da solução de matt usando \d(e chaves) no lugar de [0-9]:

grep -P '\d{4}' file | grep -Pv '\d{5}'

Como usa [0-9], o modo de matt é mais portátil - ele funcionará em sistemas onde grepnão suporta expressões regulares do Perl. Se você usar [0-9](ou [[:digit:]]) em vez de \d, mas continuar usando { }, você obtém a portabilidade do modo matt de forma um pouco mais concisa:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

Maneira alternativa, com um único padrão

Se você realmente prefere um grepcomando que

  1. usa uma única expressão regular (não dois greps separados por um tubo , como acima)
  2. para exibir linhas que contêm pelo menos uma sequência de quatro dígitos,
  3. mas nenhuma sequência de cinco (ou mais) dígitos,
  4. e você não se importa de combinar a linha inteira, não apenas os dígitos (você provavelmente não se importa)

... então você pode usar:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

A -xsinalização grepexibe apenas as linhas onde a linha inteira corresponde (em vez de qualquer linha que contenha uma correspondência).

Eu usei uma expressão regular Perl porque acho que a brevidade \de \Daumente substancialmente a clareza nesse caso. Mas se você precisar de algo portátil para sistemas onde grepnão suporta -P, poderá substituí-los por [0-9]e [^0-9](ou por [[:digit:]]e [^[:digit]]):

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

A maneira como essas expressões regulares funcionam é:

  • No meio, \d{4}ou [0-9]{4}corresponde a uma sequência de quatro dígitos. Podemos ter mais de um deles, mas precisamos ter pelo menos um.

  • À esquerda, (\d{0,4}\D)*ou ([0-9]{0,4}[^0-9])*corresponde a zero ou mais ( *) instâncias de não mais que quatro dígitos seguidos por um não dígito. Zero dígitos (ou seja, nada) é uma possibilidade para "não mais que quatro dígitos". Isso corresponde (a) à string vazia ou (b) a qualquer string que termine com um não dígito e não contenha nenhuma sequência com mais de quatro dígitos.

    Como o texto imediatamente à esquerda da central \d{4}(ou [0-9]{4}) deve estar vazio ou terminar com um não dígito, isso impede que a central \d{4}corresponda a quatro dígitos que possuem outro (quinto) dígito à esquerda deles.

  • À direita, (\D\d{0,4})*ou ([^0-9][0-9]{0,4})*corresponde a zero ou mais ( *) instâncias de um não dígito seguido por não mais de quatro dígitos (que, como antes, poderiam ser quatro, três, dois, um ou até mesmo nenhum). Isso corresponde (a) à sequência vazia ou (b) a qualquer sequência iniciada em um dígito e que não contenha nenhuma sequência de mais de quatro dígitos.

    Como o texto imediatamente à direita da central \d{4}(ou [0-9]{4}) deve estar vazio ou começar com um não-dígito, isso impede que a central \d{4}corresponda a quatro dígitos que possuem outro (quinto) dígito à direita deles.

Isso garante que uma sequência de quatro dígitos esteja presente em algum lugar e que nenhuma sequência de cinco ou mais dígitos esteja presente em qualquer lugar.

Não é ruim ou errado fazê-lo dessa maneira. Mas talvez o motivo mais importante para considerar essa alternativa seja o fato de ela esclarecer o benefício de usar (ou similar), como sugerido acima e na resposta de matt .grep -P '\d{4}' file | grep -Pv '\d{5}'

Dessa forma, fica claro que seu objetivo é selecionar linhas que contêm uma coisa, mas não outra. Além disso, a sintaxe é mais simples (portanto, pode ser compreendida mais rapidamente por muitos leitores / mantenedores).

Eliah Kagan
fonte
9

Isso mostrará quatro números seguidos, mas não mais

grep '[0-9][0-9][0-9][0-9][^0-9]' file

Observe que ^ significa não

Há um problema com isso, embora não tenha certeza de como corrigir ... se o número for o fim da linha, ele não aparecerá.

Esta versão mais feia, no entanto, funcionaria nesse caso

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]
mate
fonte
oops, não tinha necessidade de ser egrep - eu editei
mate
2
O primeiro está errado - encontra a12345b, porque combina 2345b.
Volker Siegel
0

Se grepnão suportar expressões regulares perl ( -P), use o seguinte comando shell:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

onde printf '[0-9]%.0s' {1..4}produzirá 4 vezes [0-9]. Esse método é útil quando você tem dígitos longos e não deseja repetir o padrão (substitua 4pelo número de dígitos que deseja procurar).

Usar -wprocurará as palavras inteiras. No entanto, se você estiver interessado em cadeias alfanuméricas, como 1234a, adicione [^0-9]no final do padrão, por exemplo

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

Usar $()é basicamente uma substituição de comando . Verifique esta postagem para ver como se printfrepete o padrão.

kenorb
fonte
0

Você pode tentar o comando abaixo, substituindo pelo filenome do arquivo real no seu sistema:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Você também pode verificar este tutorial para obter mais usos do comando grep.

Mike Tyson
fonte