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

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

Estou aprendendo Linux e tenho um desafio que não consigo 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 pesquisar números específicos, mas não o valor deles em uma string.

Responder1

Existem duas maneiras de interpretar esta questão; Abordarei ambos os 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 usaExpressões regulares Perl, qual Ubuntugrep(grep GNU) suporta via -P. Não corresponderá a textos como 12345, nem corresponderá a 1234ou 2345que fazem parte dele.Mas vai corresponder ao 1234in 1234a56789.

Em expressões regulares Perl:

  • \dsignifica qualquer dígito (é uma forma abreviada de dizer [0-9]ou [[:digit:]]).
  • x{4}partidasx4 vezes. ( a sintaxe não é específica para expressões regulares Perl; também { }está em expressões regulares estendidas .) Portanto , é o mesmo que .grep -E\d{4}\d\d\d\d
  • (?<!\d)é uma afirmação look-behind negativa de largura zero. Significa "a menos que precedido por \d."
  • (?!\d)é uma afirmação antecipada negativa de largura zero. Significa "a menos que seguido por \d."

(?<!\d)e (?!\d)não corresponda ao texto fora da sequência de quatro dígitos; em vez disso, eles (quando usados ​​em conjunto) 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 lookahead é insuficiente porque a subsequência de quatro dígitos mais à direita ou mais à esquerda ainda seria correspondida.

Um benefício de usarafirmações de olhar para trás e para frenteé que seu padrão corresponde apenas às sequências de quatro dígitos, e não ao texto ao redor. Isso é útil ao usar realce de cores (com a --coloropção).

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

Por padrãono Ubuntu, cada usuário tem alias grep='grep --color=auto'em seu~.bashrcarquivo. Assim, você obtém realce de cores automaticamente ao executar um comando simples começando com grep(é quandoapelidosão expandidos) esaída padrãoéum terminal(isso é o que--color=autoverifica). As correspondências normalmente são destacadas em um tom de vermelho (próximo devermelhão), mas mostrei em itálico e negrito.Aqui está uma captura de tela:
Captura de tela mostrando o comando grep, com 12345abc789d0123e4 como saída, com 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

Caminho alternativo,SemAsserções Look-Behind e Look-Ahead

No entanto, se você:

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

...então você pode conseguir isso com umexpressão regular estendidaem vez de:

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

Isso corresponde a quatro dígitos e ao caractere não numérico - ou início ou fim da linha - que os cerca. Especificamente:

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

Portanto, as correspondências ocorrem apenas em linhas contendo 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êmqualquersequência de mais de quatro dígitos (mesmo uma que seja separada de outra sequência de apenas quatro dígitos), então, conceitualmente, seu objetivo é encontrar linhas que correspondam a um padrão, mas não a outro.

Portanto, mesmo que você saiba fazer isso com um único padrão, sugiro usar algo comoMattsegunda sugestão, grepbuscando os dois padrões separadamente.

Você não se beneficia muito de nenhum dos recursos avançados das expressões regulares Perl ao fazer isso, então você pode preferir não usá-las. Mas, mantendo o estilo acima, aqui está uma abreviação desolução de Mattusando \d(e chaves) no lugar de [0-9]:

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

Uma vez que usa [0-9],jeito de Matté mais portátil - funcionará em sistemas onde grepnão suporta expressões regulares Perl. Se você usar [0-9](or [[:digit:]]) em vez de \d, mas continuar a usar { }, você obterá a portabilidade do método 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 umcano, 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 em combinar a linha inteira, não apenas os dígitos (você provavelmente não se importa com isso)

... então você pode usar:

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

O -xsinalizador grepexibe apenas as linhas onde a linha inteira corresponde (em vez de qualquer linhacontendouma partida).

Usei uma expressão regular Perl porque acho que a brevidade \de \Daumenta substancialmente a clareza neste caso. Mas se você precisar de algo portátil para sistemas onde grepnão há suporte -P, você pode 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 forma 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 ( *) ocorrências de no máximo quatro dígitos seguidas 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)a string vazia ou(b)qualquer sequênciafinalem um número sem dígito e não contendo sequências de 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 evita \d{4}que a central corresponda a quatro dígitos que tenham outro (quinto) dígito logo à 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 que quatro dígitos (que, como antes, podem ser quatro, três, dois, um ou até mesmo nenhum). Isso corresponde(a)a string vazia ou(b)qualquer sequênciacomeçoem um número sem dígito e não contendo sequências 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 evita \d{4}que a central corresponda a quatro dígitos que tenham outro (quinto) dígito logo à 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 algum lugar.

Não é ruim ou errado fazer assim. Mas talvez a razão mais importante para considerar esta alternativa é que ela esclarece o benefício de usar (ou similar), como sugerido acima e emgrep -P '\d{4}' file | grep -Pv '\d{5}'resposta de Matt.

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

Responder2

Isso mostrará 4 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, mas não tenho certeza de como consertar... se o número for o fim da linha, ele não aparecerá.

Esta versão mais feia, entretanto, funcionaria para esse caso

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]

Responder3

Você pode tentar o comando abaixo substituindo filepelo nome real do arquivo em seu sistema:

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

Você também pode verificareste tutorialpara mais usos do comando grep.

Responder4

Se grepnão for compatível com 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]. Este método é útil quando você tem dígitos longos e não deseja repetir o padrão (basta substituir 4pelo número dos dígitos que deseja procurar).

Usar -wirá procurar as palavras inteiras. No entanto, se você estiver interessado em strings 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 umsubstituição de comando. Verifique issopublicarpara ver como printfo padrão se repete.

informação relacionada