Recherche Regex pour 'non suivi de' dans grep

104

J'essaie de grep pour toutes les instances de Ui\.non suivies Lineou même simplement de la lettreL

Quelle est la bonne façon d'écrire une expression régulière pour trouver toutes les instances d'une chaîne particulière NON suivie d'une autre chaîne?

Utiliser des lookaheads

grep "Ui\.(?!L)" *
bash: !L: event not found


grep "Ui\.(?!(Line))" *
nothing
Lee Quarella
la source
5
Quelle sous-espèce de regex - PCRE, ERE, BRE, grep, ed, sed, perl, python, Java, C, ...?
Jonathan Leffler
4
En passant, «l'événement introuvable» provient de l'utilisation de l'expansion de l'historique. Vous pouvez désactiver l'expansion de l'historique si vous ne l'utilisez jamais et souhaitez parfois pouvoir utiliser un point d'exclamation dans vos commandes interactives. set +o histexpanddans Bash ou set +H, YMMV.
tripleee
12
J'ai également eu le problème d'expansion de l'historique. Je pense que je l'ai résolu simplement en passant aux guillemets simples, de sorte que le shell n'essaierait pas de briser l'argument.
Coderer le
@Coderer qui a également résolu mon problème. Merci.
NHDaly

Réponses:

151

La recherche négative, ce que vous recherchez, nécessite un outil plus puissant que la norme grep . Vous avez besoin d'un grep compatible PCRE.

Si vous avez GNU grep, la version actuelle prend en charge les options -Pou--perl-regexp et vous pouvez alors utiliser l'expression régulière que vous souhaitez.

Si vous n'avez pas (une version suffisamment récente de) GNU grep, envisagez de vous procurer ack.

Jonathan Leffler
la source
37
Je suis à peu près sûr que le problème dans ce cas est simplement que dans bash, vous devez utiliser des guillemets simples et non des guillemets doubles afin de ne pas le traiter !comme un caractère spécial.
NHDaly
(voir ci-dessous ma réponse décrivant exactement cela.)
NHDaly
4
La réponse vérifiée et correcte doit combiner cette réponse et le commentaire de @ NHDaly. Par exemple, cette commande fonctionne pour moi: grep -P '^. * Contient ((?! Mais_pas_ceci).) * $' * .Log. *> "D: \ temp \ result.out"
wangf
3
Pour ceux où -Pne sont pas pris en charge résultat de la tuyauterie d'essayer à nouveau grep --invert-match, ex: git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'. Assurez-vous de voter pour la réponse de @Vinicius Ottoni.
Daniel Sokolowski
@wangf J'utilise Bash sous Cygwin et quand je passe aux guillemets simples, j'obtiens toujours l'erreur "événement non trouvé".
SSilk
41

La réponse à une partie de votre problème est ici, et ack se comporterait de la même manière: Ack & lookahead négatif donnant des erreurs

Vous utilisez des guillemets doubles pour grep, ce qui permet à bash «d'interpréter !comme commande de développement de l'historique».

Vous devez envelopper votre modèle de guillemets uniques: grep 'Ui\.(?!L)' *

Cependant, consultez la réponse de @ JonathanLeffler pour résoudre les problèmes liés aux anticipations négatives en standard grep!

NHDaly
la source
Vous confondez la fonctionnalité d'extension de GNU grepavec la fonctionnalité de standard grep, où le standard pour grepest POSIX. Ce que vous dites est également vrai - je lance Bash avec les barbarismes C-shell désactivés (parce que si je voulais un shell C, j'en utiliserais un, mais je n'en veux pas), donc les !choses ne m'affectent pas - mais pour obtenir des lookaheads négatifs, vous avez besoin de non standard grep.
Jonathan Leffler
1
@JonathanLeffler, merci pour la clarification; Je pense que vous avez raison de dire qu'il faut que nos deux réponses traitent tous les symptômes du PO. Merci.
NHDaly
11

Vous ne pouvez probablement pas effectuer de lookaheads négatifs standard en utilisant grep, mais vous devriez généralement pouvoir obtenir un comportement équivalent en utilisant le commutateur «inverse» '-v'. En utilisant cela, vous pouvez construire une regex pour le complément de ce que vous voulez faire correspondre, puis la diriger à travers 2 greps.

Pour le regex en question, vous pouvez faire quelque chose comme

grep 'Ui\.' * | grep -v 'Ui\.L'
Karel Tucek
la source
Cela exclurait plus de choses, plus d'instance si la ligne contient Ui.Line et Ui sans .Line
nafg
1
(Oui, c'est pourquoi je ne le formule pas strictement. Cela résout simplement une partie importante des scénarios qui dirigent les gens vers ce problème, rien de plus.)
Karel Tucek
4

Si vous avez besoin d'utiliser une implémentation de regex qui ne prend pas en charge les anticipations de recherche négatives et que cela ne vous dérange pas de faire correspondre des caractères supplémentaires *, vous pouvez utiliser des classes de caractères annulés[^L] , une alternance| et la fin de l'ancre de chaîne$ .

Dans votre cas grep 'Ui\.\([^L]\|$\)' *fait le travail.

  • Ui\. correspond à la chaîne qui vous intéresse

  • \([^L]\|$\)correspond à tout caractère unique autre que Lou correspond à la fin de la ligne: [^L]ou $.

Si vous souhaitez exclure plus d'un caractère, il vous suffit de lui ajouter plus d'alternance et de négation. Pour trouver anon suivi de bc:

grep 'a\(\([^b]\|$\)\|\(b\([^c]\|$\)\)\)' *

Qui est soit ( asuivi de non bou suivi de la fin de la ligne: aalors [^b]ou $) ou ( asuivi de bqui est soit suivi de non, csoit suivi de la fin de la ligne: aalors b, alors [^c]ou $.

Ce type d'expression devient assez lourd et sujet aux erreurs même avec une chaîne courte. Vous pouvez écrire quelque chose pour générer les expressions à votre place, mais il serait probablement plus facile d'utiliser simplement une implémentation de regex qui prend en charge les anticipations négatives.

* Si votre implémentation prend en charge les groupes sans capture, vous pouvez éviter de capturer des caractères supplémentaires.

dougcosine
la source
1

Si votre grep ne prend pas en charge -P ou --perl-regexp, et que vous pouvez installer un grep compatible PCRE, par exemple "pcregrep", il n'aura pas besoin d'options de ligne de commande comme GNU grep pour accepter le standard compatible Perl expressions, vous venez de courir

pcregrep "Ui\.(?!Line)"

Vous n'avez pas besoin d'un autre groupe imbriqué pour "Line" comme dans votre exemple "Ui. (?! (Line))" - le groupe externe est suffisant, comme je l'ai montré ci-dessus.

Permettez-moi de vous donner un autre exemple de recherche d'assertions négatives: lorsque vous avez une liste de lignes, renvoyée par «ipset», chaque ligne indiquant le nombre de paquets au milieu de la ligne, et que vous n'avez pas besoin de lignes avec zéro paquet, vous courir:

ipset list | pcregrep "packets(?! 0 )"

Si vous aimez les expressions régulières compatibles avec Perl et que vous avez perl mais que vous n'avez pas pcregrep ou que votre grep ne prend pas en charge --perl-regexp, vous pouvez utiliser des scripts perl sur une ligne qui fonctionnent de la même manière que grep:

perl -e "while (<>) {if (/Ui\.(?!Lines)/){print;};}"

Perl accepte stdin de la même manière que grep, par exemple

ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
Maxim Masiutin
la source