Com o grep, como posso corresponder um padrão e inverter a correspondência com outro padrão?

10

Com grep , desejo selecionar todas as linhas que correspondem a um padrão e que não correspondam a outro padrão. Eu quero ser capaz de usar uma única chamada de grep para que eu possa usar a opção --after-context (ou --before-context , ou --context ).

-v não é viável aqui, pois nega todos os padrões que eu passo para grep usando a opção -e .

Exemplo

Eu quero procurar linhas que combinem needle , ignorando as linhas que correspondem a ignore me , com uma linha do contexto a seguir.

Aqui está o meu arquivo de entrada:

one needle ignore me
two
three
four needle
five

A saída que quero é:

four needle
five

Como você pode ver, essa solução ingênua não funciona:

$ cat file | grep --after-context=1 needle | grep -v 'ignore me'
two
---
four needle
five
    
por Flimm 17.10.2013 / 15:52

3 respostas

7

Se você tem o GNU grep, você pode usar expressões regulares Perl , que têm um construção de negação .

grep -A1 -P '^(?!.*ignore me).*needle'

Se você não tem o GNU grep, você pode emula suas opções de contexto antes / depois no awk .

awk -v after=3 -v before=2 '
/needle/ && !/ignore me/ {
    for (i in h) {
        print h[i];
        delete h[i];
    }
    until = NR + after;
}
{
    if (NR <= until) print $0; else h[NR] = $0;
    delete h[NR-before];
}
END {exit !until}
'
    
por 18.10.2013 / 00:58
8

Você parece estar usando o GNU . Com o GNU grep, você poderia passar o --perl-regex flag para ativar o PCRE e então fornecer uma declaração lookahead negativa, exemplo abaixo

grep --after-context=1 \
--perl-regex '^(?:(?!ignore me).)*needle(?:(?!ignore me).)*$' file.txt
four needle
five

O principal ponto de nota aqui é que (?:(?!STRING).)* é para STRING como [^CHAR]* é para CHAR

    
por 17.10.2013 / 16:02
2

Eu sugeriria usar o awk ao invés de manipular multi-linha IO melhor. Ou 1) canaliza os resultados para o GNU awk com --\n como o separador de registro, ou 2) faz toda a correspondência no awk.

Opção 1

<file grep -A1 needle | awk '!/ignore me/' RS='--\n' ORS='--\n'

Saída:

four needle                                                                                  
five
--

Observe que essa opção pesquisa o registro inteiro por ignore me , define FS=1 e corresponde a $1 para comparar apenas com a primeira linha.

Opção 2

<file awk 'a-- > 0; $0 ~ re1 && $0 !~ re2 { print $0; a=after }' re1=needle re2='ignore me' after=1
    
por 17.10.2013 / 16:38

Tags