Com grep, como posso corresponder a um padrão e inverter corresponder a outro padrão?


11

Com grep, desejo selecionar todas as linhas que correspondam a um padrão e que não correspondam a outro padrão. Quero poder usar uma única invocação de greppara poder usar a --after-contextopção (ou --before-context, ou --context).

-vnão é viável aqui, pois nega todos os padrões que passo ao grepusar a -eopção

Exemplo

Quero procurar linhas correspondentes needle, ignorando as linhas correspondentes ignore me, com uma linha do seguinte contexto.

Aqui está o meu arquivo de entrada:

one needle ignore me
two
three
four needle
five

A saída que eu quero é:

four needle
five

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

$ cat file | grep --after-context=1 needle | grep -v 'ignore me'
two
---
four needle
five

Respostas:


10

Se você possui o GNU grep, pode usar expressões regulares do Perl , que possuem uma construção de negação .

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

Se você não possui o GNU grep, pode emular 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}
'

8

Você parece estar usando o GNU . Com o GNU grep, você pode passar o --perl-regexsinalizador para ativar o PCRE e fornecer uma afirmação negativa do lookahead, exemplo abaixo

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

A principal coisa a notar aqui é que (?:(?!STRING).)*é STRINGcomo [^CHAR]*éCHAR


@ 1_CR ... Sir .. é incrível ..: P algo mais desagradável para:ack
Rahul Patil

@RahulPatil. :-), sim, o GNU grep é bom.
Iruvar

Não é bem isso que eu quero. Quero que funcione se "me ignore" é antes ou depois da "agulha".
Flimm

@RahulPatil, obrigado, eu fixa-lo na versão mais recente
Iruvar

Muito útil. Especialmente no caso de grep com contexto em que você deseja excluir linhas que correspondam muito bem, mas sem uma certa parte do padrão. Perto da pergunta original, mas não é a mesma coisa.
gaoithe

2

Eu sugeriria o uso do awk, pois ele lida com IO de várias linhas melhor. Ou 1) canalizar os resultados para GNU awk com --\nque o separador de registro, ou 2) Faça tudo de concordância, por awk.

Opção 1

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

Resultado:

four needle                                                                                  
five
--

Observe que esta opção pesquisa ignore me, define FS=1e $1compara todo o registro 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

Existe múltipla ignore meem arquivo, em seguida, o awk não funciona
Rahul Patil

@RahulPatil: você poderia reformular ou adicionar mais detalhes à sua pergunta? Eu não entendo o que você está perguntando.
Thor

@Thos teste seu exemplo com este arquivo de entrada paste.ubuntu.com/6252860
Rahul Patil

@RahulPatil: Entendo o que você quer dizer agora, a Opção 1 assume que um --\ndelimitador está entre cada grupo correspondente, o que não existe se os grupos estiverem adjacentes um ao outro. Como os grupos adjacentes devem ser tratados é específico da tarefa, portanto isso não é necessariamente errado. A opção 2 não depende do separador e não é afetada.
Thor
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.