Regex cerca in anticipo per "non seguito da" in grep


103

Sto tentando di grep per tutti i casi di Ui\.non seguito da Lineo anche solo per la letteraL

Qual è il modo corretto di scrivere una regex per trovare tutte le istanze di una particolare stringa NON seguite da un'altra stringa?

Usare i lookahead

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


grep "Ui\.(?!(Line))" *
nothing

5
Quali sottospecie di regex - PCRE, ERE, BRE, grep, ed, sed, perl, python, Java, C, ...?
Jonathan Leffler

4
Per inciso, l '"evento non trovato" deriva dall'utilizzo dell'espansione della cronologia. Potresti voler disattivare l'espansione della cronologia se non la usi e talvolta vuoi essere in grado di usare un punto esclamativo nei tuoi comandi interattivi. set +o histexpandin Bash o set +H, YMMV.
tripla

12
Ho anche avuto il problema dell'espansione della cronologia. Mi pare ho risolto semplicemente passando a apici, quindi il guscio non avrebbe cercato di munge l'argomento.
Coderer

@Coderer che ha risolto anche il mio problema. Grazie.
NHDaly

Risposte:


151

Il lookahead negativo, che è quello che stai cercando, richiede uno strumento più potente dello standard grep. Hai bisogno di un grep abilitato per PCRE.

Se si dispone di GNU grep, le attuali opzioni supporta la versione -Po --perl-regexpe si può quindi utilizzare l'espressione regolare che si voleva.

Se non hai (una versione sufficientemente recente di) GNU grep, allora considera di ottenere ack.


37
Sono abbastanza sicuro che il problema in questo caso sia solo che in bash dovresti usare virgolette singole e non virgolette doppie, quindi non verrà trattato !come un carattere speciale.
NHDaly

(vedi sotto per la mia risposta che descrive esattamente questo.)
NHDaly

4
La risposta corretta e verificata dovrebbe combinare questa risposta e il commento di @ NHDaly. Ad esempio, questo comando funziona per me: grep -P '^. * Contiene ((?! But_not_this).) * $' * .Log. *> "D: \ temp \ result.out"
wangf

3
Per quelli in cui -Pnon è supportato ancora una volta prova risultato tubazioni per grep --invert-match, es: git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'. Assicurati di votare la risposta di @Vinicius Ottoni.
Daniel Sokolowski

@wangf Sto usando Bash sotto Cygwin e quando passo alle virgolette singole, ricevo ancora l'errore "evento non trovato".
SSilk

40

La risposta a una parte del tuo problema è qui, e ack si comporterebbe allo stesso modo: Ack e lookahead negativo che danno errori

Stai usando le virgolette doppie per grep, che consente a bash di "interpretare !come comando di espansione della cronologia".

Devi racchiudere il tuo motivo in CITAZIONI SINGOLE: grep 'Ui\.(?!L)' *

Tuttavia, vedi la risposta di @ JonathanLeffler per affrontare i problemi con lookahead negativi in ​​standard grep!


Stai confondendo la funzionalità di estensione di GNU grepcon la funzionalità di standard grep, dove lo standard grepè POSIX. Anche quello che dici è vero: eseguo Bash con le barbarie della shell C disabilitate (perché se volessi una shell C, ne userei una, ma non ne voglio una), quindi le !cose non mi influenzano - ma per ottenere lookahead negativi, è necessario non standard grep.
Jonathan Leffler

1
@JonathanLeffler, grazie per il chiarimento; Penso che tu abbia ragione sul fatto che siano necessarie entrambe le nostre risposte per affrontare tutti i sintomi dell'OP. Grazie.
NHDaly

11

Probabilmente non puoi eseguire lookahead negativi standard usando grep, ma di solito dovresti essere in grado di ottenere un comportamento equivalente usando l'opzione "inversa" '-v'. Usandolo puoi costruire una regex per il complemento di ciò che vuoi abbinare e poi convogliarlo attraverso 2 greps.

Per la regex in questione potresti fare qualcosa di simile

grep 'Ui\.' * | grep -v 'Ui\.L'

Ciò escluderebbe più cose, più casi se la riga contiene Ui.Line e Ui senza
.Line

1
(Sì, è per questo che non lo formulo rigorosamente. Questo risolve semplicemente una parte significativa di scenari che portano le persone a questo problema, niente di più.)
Karel Tucek

4

Se è necessario utilizzare un'implementazione regex che non supporta lookahead negativi e non ti dispiace abbinare caratteri aggiuntivi *, puoi utilizzare classi di caratteri negati[^L] , alternanza| e la fine dell'ancora di stringa$ .

Nel tuo caso grep 'Ui\.\([^L]\|$\)' *fa il lavoro.

  • Ui\. corrisponde alla stringa che ti interessa

  • \([^L]\|$\)corrisponde a qualsiasi carattere singolo diverso da Lo corrisponde alla fine della riga: [^L]o $.

Se vuoi escludere più di un personaggio, devi solo aggiungere più alternanze e negazioni. Per trovare anon seguito da bc:

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

Che è ( aseguito da no bo seguito dalla fine della riga: aquindi [^b]o $) o ( aseguito da bcui è seguito da no co è seguito dalla fine della riga: athen b, then [^c]or $.

Questo tipo di espressione diventa piuttosto ingombrante e soggetto a errori anche con una stringa breve. Potresti scrivere qualcosa per generare le espressioni per te, ma probabilmente sarebbe più facile usare solo un'implementazione regex che supporta lookahead negativi.

* Se la tua implementazione supporta gruppi non di acquisizione, puoi evitare di acquisire caratteri extra.


1

Se il tuo grep non supporta -P o --perl-regexp, e puoi installare grep abilitato per PCRE, ad esempio "pcregrep", allora non avrà bisogno di alcuna opzione della riga di comando come GNU grep per accettare il normale compatibile con Perl espressioni, corri e basta

pcregrep "Ui\.(?!Line)"

Non hai bisogno di un altro gruppo nidificato per "Line" come nel tuo esempio "Ui. (?! (Line))" - il gruppo esterno è sufficiente, come ho mostrato sopra.

Consentitemi di darvi un altro esempio di ricerca di asserzioni negative: quando avete un elenco di righe, restituito da "ipset", ogni riga mostra il numero di pacchetti al centro della riga e non avete bisogno di righe con zero pacchetti, basta correre:

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

Se ti piacciono le espressioni regolari compatibili con perl e hai perl ma non hai pcregrep o il tuo grep non supporta --perl-regexp, puoi usare script perl di una riga che funzionano allo stesso modo come grep:

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

Perl accetta stdin allo stesso modo di grep, ad es

ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.