Existem duas maneiras de interpretar essa questão; Vou abordar os dois casos. Você pode querer exibir linhas:
- que contêm uma sequência de quatro dígitos que não faz parte de uma sequência mais longa de dígitos, ou
- que contém uma sequência de quatro dígitos, mas não mais sequência de dígitos (nem mesmo separadamente).
Por exemplo, (1) exibe 1234a56789
, mas (2) não.
Se você deseja exibir todas as linhas que contêm uma seqüência de quatro dígitos que não faz parte de uma sequência mais longa de dígitos, uma delas é:
grep -P '(?<!\d)\d{4}(?!\d)' file
Isso usa expressões regulares Perl , que são do Ubuntu grep
( GNU grep ) suporta via -P
. Ele não corresponderá ao texto como 12345
nem corresponderá ao 1234
ou 2345
que fazem parte dele. Mas corresponderá à 1234
em 1234a56789
.
Em expressões regulares Perl:
-
\d
significa qualquer dígito (é um caminho curto para dizer[0-9]
ou[[:digit:]]
). -
x{4}
corresponde ax
4 vezes. ({
}
sintaxe não é específica para expressões regulares Perl; também está em expressões regulares estendidas viagrep -E
.) Então\d{4}
é o mesmo que\d\d\d\d
. -
(?<!\d)
é uma asserção negativa de largura zero. Significa "a menos que precedido por\d
". -
(?!\d)
é uma asserção de look-ahead negativa de largura zero. Significa "a menos que seja seguido por\d
".
(?<!\d)
e (?!\d)
não correspondem a 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 for 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 mais à esquerda ainda seria correspondida.
Um benefício do uso de asserções atrasadas e look-ahead é que seu padrão corresponde apenas aos quatro seqüências de dígitos, e não o texto ao redor. Isso é útil ao usar o realce de cor (com a opção --color
).
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 ~.bashrc
file . Assim, você obtém destaque de cor automaticamente quando você executa um comando simples que começa com grep
(isto é, quando aliases são expandidos) e a saída padrão é um terminal (é isso que é --color=auto
verifica se há). As correspondências são normalmente destacadas em um tom de vermelho (perto de vermelhão ), mas mostrei em negrito em itálico. Aqui está uma imagem:
E você pode até fazer grep
imprimir apenas o texto correspondente, e não a linha inteira, com -o
:
ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123
Alternative Way, Sem Look-Behind e Look-Ahead Assertions
No entanto, se você:
- precisa de um comando que também seja executado em sistemas em que
grep
não suporte-P
ou, caso contrário, não queira usar uma expressão regular Perl, e - não precisa corresponder aos quatro dígitos especificamente - o que geralmente é o caso se seu objetivo é simplesmente exibir linhas contendo correspondências, e
- estão bem com uma solução que é 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
Isso corresponde a quatro dígitos e o caractere não digitável - ou início ou fim da linha - em torno deles. Especificamente:
-
[0-9]
corresponde a qualquer dígito (como[[:digit:]]
ou\d
em expressões regulares Perl) e{4}
significa "quatro vezes". Então,[0-9]{4}
corresponde a uma sequência de quatro dígitos. -
[^0-9]
corresponde a caracteres que não estão no intervalo de0
a9
. É equivalente a[^[:digit:]]
(ou\D
, em expressões regulares Perl). -
^
, quando não aparece em[
]
colchetes, corresponde ao início de uma linha. Da mesma forma,$
corresponde ao final de uma linha. -
|
significa ou e parênteses são para agrupamento (como na álgebra).Portanto,(^|[^0-9])
corresponde ao início da linha ou a um caractere não digitável, enquanto($|[^0-9])
corresponde ao final da linha ou a um caractere não dígito.
Portanto, as correspondências ocorrem apenas nas 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ê quiser exibir todas as linhas que contenham uma sequência de quatro dígitos, mas não contenha nenhuma seqüência com mais de quatro dígitos (mesmo uma separada de outra) seqüê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 como fazer isso com um único padrão, sugiro usar algo como matt's segunda sugestão, grep
ing para 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, portanto talvez prefira não usá-los. Mas, de acordo com o estilo acima, aqui está um encurtamento da solução da matt usando \d
(e chaves) no lugar de [0-9]
:
grep -P '\d{4}' file | grep -Pv '\d{5}'
Como ele usa [0-9]
, o caminho do matt é mais portátil - ele funcionará em sistemas em que grep
não suporta expressões regulares Perl. Se você usa [0-9]
(ou [[:digit:]]
) em vez de \d
, mas continua a usar {
}
, você obtém a portabilidade do caminho de Matt de maneira um pouco mais concisa:
grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'
Caminho alternativo, com um único padrão
Se você realmente preferir um comando grep
,
-
usa uma única expressão regular (não dois
grep
s separados por um tubo , como acima) - para exibir linhas que contenham pelo menos uma sequência de quatro dígitos,
- mas sem sequências de cinco (ou mais) dígitos,
- e você não se importa em combinar a linha inteira, não apenas os dígitos (provavelmente você 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 -x
sinalizador faz com que grep
exiba apenas as linhas em que a linha inteira corresponde (em vez de qualquer linha contendo uma correspondência).
Eu usei uma expressão regular Perl porque acho que a brevidade de \d
e \D
aumenta substancialmente a clareza nesse caso. Mas se você precisar de algo portátil para sistemas em que grep
não suporte -P
, você poderá substituí-los por [0-9]
e [^0-9]
(ou com [[: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. Dígitos zero (isto é, nada) é uma possibilidade para "não mais de quatro dígitos". Isso corresponde a (a) a string vazia ou (b) qualquer string terminando em um não-dígito e não contendo nenhuma sequência 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 impede que a central\d{4}
corresponda a quatro dígitos que têm outra ( quinto) dígito apenas à esquerda deles. -
À direita,
(\D\d{0,4})*
ou([^0-9][0-9]{0,4})*
corresponde a zero ou mais (*
) instâncias de um dígito não seguido por não mais de quatro dígitos (que, como antes, poderia ser quatro, três, dois, um, ou mesmo nenhum em todos). Isso corresponde a (a) a string vazia ou (b) qualquer string começando em um não-dígito e não contendo 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 tenham outra (quinta ) dígito à direita deles.
Isso garante que uma seqüência de quatro dígitos esteja presente em algum lugar e que nenhuma seqüência de cinco ou mais dígitos esteja presente em lugar algum.
Não é ruim ou errado fazer isso dessa maneira. Mas talvez a razão mais importante para considerar essa alternativa é que ela esclarece o benefício de usar grep -P '\d{4}' file | grep -Pv '\d{5}'
(ou similar), como sugerido acima e em 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 (por isso, pode ser mais rapidamente compreendida por muitos leitores / mantenedores).