Existem duas maneiras de interpretar essa pergunta; 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 nenhuma sequência mais longa de dígitos, ou
- 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 1234
ou 2345
que faz parte dele. Mas vai combinar com o 1234
in 1234a56789
.
Nas expressões regulares do Perl:
\d
significa qualquer dígito (é uma maneira curta de dizer [0-9]
ou [[:digit:]]
).
x{4}
corresponde x
4 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 --color
opçã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 ~.bashrc
arquivo . 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
E você pode até grep
imprimir 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ê:
- precisa de um comando que também seja executado em sistemas onde
grep
não suporta -P
ou de outra forma não deseja usar uma expressão regular Perl, e
- não precisa corresponder especificamente aos quatro dígitos - o que geralmente acontece se seu objetivo é simplesmente exibir linhas contendo correspondências e
- 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 \d
em 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 0
meio 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 , grep
para 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 grep
nã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 grep
comando que
- usa uma única expressão regular (não dois
grep
s separados por um tubo , como acima)
- para exibir linhas que contêm pelo menos uma sequência de quatro dígitos,
- mas nenhuma sequência de cinco (ou mais) dígitos,
- 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 -x
sinalização grep
exibe 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 \d
e \D
aumente substancialmente a clareza nesse caso. Mas se você precisar de algo portátil para sistemas onde grep
nã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).
1234a12345
ser exibida ou não?