como se pode combinar uma série de instruções do grep em uma instrução grep?

4

Eu gostaria de saber se existe uma maneira de combinar uma série de declarações do grep onde o efeito é "e" as expressões ao invés de "ou" as expressões correspondentes.

Demonstração abaixo:

./script  
     From one grep statement, I want output like this
a b c

     not like this
a
c
a b
a b c
a b c d

Ouça é uma olhada no roteiro.

 #!/bin/bash
 string="a
 b
 c
 d
 a b
 a b c
 a b c d"

 echo -e "\t From one grep statement I want output like this"
 echo "$string" |
 grep a |grep c |grep -v d #Correct output but pipes three grep statements

 echo -e "\n\tNot like this"
 echo "$string" |
 grep -e'a' -e'c' -e-v'd' #One grep statement but matching expressions are "or" versus "and"
    
por Keith Reynolds 16.08.2013 / 19:33

3 respostas

6

Você não pode transformar o filtro grep a | grep c | grep -v d em um único% simples grep . Existem apenas formas complicadas e ineficazes. O resultado tem desempenho lento e o significado da expressão é obscurecido.

Combinação de comando único dos três greps

Se você quiser apenas executar um único comando, poderá usar awk , que também funciona com expressões regulares e pode combiná-las com operadores lógicos. Aqui está o equivalente do seu filtro:

awk '/a/ && /c/ && $0 !~ /d/'

Eu acho que na maioria dos casos não há razão para simplificar um pipe para um único comando, exceto quando a combinação resulta em uma expressão grep real e simples que pode ser mais rápida (veja os resultados abaixo).

Os sistemas Unix-like são projetados para usar pipes e conectar vários utilitários juntos. Embora a comunicação do tubo não seja a mais eficaz possível, mas na maioria dos casos é suficiente. Porque hoje em dia a maioria dos novos computadores tem múltiplos núcleos de CPU, você pode "naturalmente" utilizar a paralelização de CPU apenas usando um pipe!

Seu filtro original funciona muito bem e acho que, em muitos casos, a solução awk seria um pouco mais lenta, mesmo em um único núcleo.

Comparação de desempenho

Usando um programa simples , gerou um arquivo de teste aleatório com 200 000 000 linhas, cada uma com 4 caracteres como uma combinação aleatória de caracteres a , b , c e d . O arquivo tem 1 GB. Durante os testes, ele foi completamente carregado no cache, de modo que nenhuma operação de disco afetou a medição de desempenho. Os testes foram executados no dual core da Intel.

Single grep

$ time ( grep -E '^[^d]*a[^d]*c[^d]*$|^[^d]*c[^d]*a[^d]*$' testfile >/dev/null )
real    3m2.752s
user    3m2.411s
sys 0m0.252s

Único awk

$ time ( awk '/a/ && /c/ && $0 !~ /d/' testfile >/dev/null )
real    0m54.088s
user    0m53.755s
sys 0m0.304s

Os três greps originais canalizados

$ time ( grep a testfile | grep c | grep -v d >/dev/null )
real    0m28.794s
user    0m52.715s
sys 0m1.072s

Híbrido - greps positivos combinados, canalizados negativos

$ time ( grep -E 'a.*c|c.*a' testfile | grep -v d >/dev/null )
real    0m15.838s
user    0m24.998s
sys 0m0.676s

Aqui você vê que o único grep é muito lento devido à expressão complexa. O pipe original de três greps é bastante rápido por causa de uma boa paralelização. Sem paralelização - em um único núcleo - o canal original é executado apenas um pouco mais rápido que awk , o que como um processo único não é paralelizado. Awk e grep provavelmente usam o mesmo código de expressões regulares e a lógica das duas soluções é semelhante.

O vencedor claro é o hybring combinando dois greps positivos e deixando o negativo no pipe. Parece que a expressão regular com | não tem penalidade de desempenho.

    
por pabouk 30.08.2013 / 05:05
1

O problema é que -e funciona como or , não como and . Você pode fazer isso em uma linha, mas é bastante complicado. A parte não é a mais complicada.

Para simplificar as a e c parts (a ordem assumida é desconhecida):

grep -E 'a.*c|c.*a'

ou

grep -e 'a.*c' -e 'c.*a'

Assim, você poderia fazer

grep -E 'a.*c|c.*a' | grep -v 'd'

Para uma única declaração do grep, você precisará garantir que não haja d s antes, depois ou entre o a e o c :

grep -E '^[^d]*a[^d]*c[^d]*$|^[^d]*c[^d]*a[^d]*$'
    
por Sparhawk 30.08.2013 / 06:32
0

Você pode usar a opção -x , que, de acordo com a página grep man, "seleciona apenas as correspondências que correspondem exatamente à linha inteira".

No seu exemplo, tente: grep -x "a b c"

    
por user184720 16.08.2013 / 21:22

Tags