Avec grep, comment puis-je faire correspondre un motif et inverser un autre motif?


11

Avec grep, je veux sélectionner toutes les lignes qui correspondent à un motif et qui ne correspondent pas à un autre motif. Je veux pouvoir utiliser une seule invocation de grepafin de pouvoir utiliser l' --after-contextoption (ou --before-context, ou --context).

-vn'est pas viable ici, car il annule tous les modèles que je passe à l' grepaide de l' -eoption.

Exemple

Je veux rechercher des lignes correspondant needle, en ignorant les lignes correspondant ignore me, avec une ligne du contexte suivant.

Voici mon fichier d'entrée:

one needle ignore me
two
three
four needle
five

La sortie que je veux est:

four needle
five

Comme vous pouvez le voir, cette solution naïve ne fonctionne pas:

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

Réponses:


10

Si vous avez GNU grep, vous pouvez utiliser des expressions régulières Perl , qui ont une construction de négation .

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

Si vous n'avez pas GNU grep, vous pouvez émuler ses options de contexte avant / après dans 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

Vous semblez utiliser GNU . Avec GNU grep, vous pouvez passer le --perl-regexdrapeau pour activer PCRE, puis fournir une assertion d'anticipation négative, exemple ci-dessous

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

La principale chose à noter est que (?:(?!STRING).)*est à STRINGcomme [^CHAR]*est deCHAR


@ 1_CR ... Monsieur .. c'est génial ..: P quelque chose de plus souriantack
Rahul Patil

@RahulPatil. :-), oui GNU grep est si bon.
iruvar

Ce n'est pas tout à fait ce que je veux. Je veux que ça marche, que "m'ignorer" soit avant ou après "aiguille".
Flimm

@RahulPatil, merci, je l'ai corrigé dans la dernière version
iruvar

Très utile. Surtout dans le cas de grep avec un contexte où vous souhaitez exclure des lignes proches mais sans une certaine partie du motif. Proche de la question d'origine mais pas tout à fait la même.
gaoithe

2

Je suggérerais d'utiliser awk à la place car il gère mieux les E / S multi-lignes. Soit 1) Dirigez les résultats vers GNU awk avec --\ncomme séparateur d'enregistrement, ou 2) Faites toutes les correspondances dans awk.

Option 1

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

Production:

four needle                                                                                  
five
--

Remarque, cette option recherche l'ensemble de l'enregistrement ignore me, le définit FS=1et le $1compare pour comparer uniquement à la première ligne.

Option 2

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

Y a-t-il plusieurs ignore mefichiers dans le fichier alors, awk ne fonctionne pas
Rahul Patil

@RahulPatil: pourriez-vous reformuler ou ajouter plus de détails à votre question? Je ne comprends pas ce que vous demandez.
Thor

@Thos teste votre exemple avec ce fichier d'entrée paste.ubuntu.com/6252860
Rahul Patil

@RahulPatil: Je vois ce que vous voulez dire maintenant, l' option 1 suppose qu'un --\ndélimiteur se trouve entre chaque groupe correspondant, qui n'est pas là si les groupes sont adjacents les uns aux autres. La façon dont les groupes adjacents doivent être gérés est spécifique à la tâche, ce n'est donc pas nécessairement faux. L'option 2 ne dépend pas du séparateur et n'est pas affectée.
Thor
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.