regex101 - Espressione regolare per abbinare una linea che non contiene una parola?




regex not (18)

benchmark

Ho deciso di valutare alcune delle opzioni presentate e confrontare le loro prestazioni, nonché utilizzare alcune nuove funzionalità. Benchmarking su .NET Regex Engine: http://regexhero.net/tester/

Testo di riferimento:

Le prime 7 righe non devono corrispondere, poiché contengono l'espressione cercata, mentre le 7 righe inferiori devono corrispondere!

Regex Hero is a real-time online Silverlight Regular Expression Tester.
XRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex HeroRegex HeroRegex HeroRegex HeroRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her Regex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.Regex Hero
egex Hero egex Hero egex Hero egex Hero egex Hero egex Hero Regex Hero is a real-time online Silverlight Regular Expression Tester.
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRegex Hero is a real-time online Silverlight Regular Expression Tester.

Regex Her
egex Hero
egex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her is a real-time online Silverlight Regular Expression Tester.
Nobody is a real-time online Silverlight Regular Expression Tester.
Regex Her o egex Hero Regex  Hero Reg ex Hero is a real-time online Silverlight Regular Expression Tester.

risultati:

I risultati sono Iterazioni al secondo come mediana di 3 esecuzioni - Numero più grande = Migliore

01: ^((?!Regex Hero).)*$                    3.914   // Accepted Answer
02: ^(?:(?!Regex Hero).)*$                  5.034   // With Non-Capturing group
03: ^(?>[^R]+|R(?!egex Hero))*$             6.137   // Lookahead only on the right first letter
04: ^(?>(?:.*?Regex Hero)?)^.*$             7.426   // Match the word and check if you're still at linestart
05: ^(?(?=.*?Regex Hero)(?#fail)|.*)$       7.371   // Logic Branch: Find Regex Hero? match nothing, else anything

P1: ^(?(?=.*?Regex Hero)(*FAIL)|(*ACCEPT))  ?????   // Logic Branch in Perl - Quick FAIL
P2: .*?Regex Hero(*COMMIT)(*FAIL)|(*ACCEPT) ?????   // Direct COMMIT & FAIL in Perl

Poiché .NET non supporta i verbi di azione (* FAIL, ecc.) Non è stato possibile testare le soluzioni P1 e P2.

Sommario:

Ho provato a testare la maggior parte delle soluzioni proposte, alcune ottimizzazioni sono possibili per determinate parole. Ad esempio, se le prime due lettere della stringa di ricerca non sono le stesse, la risposta 03 può essere espansa in ^(?>[^R]+|R+(?!egex Hero))*$ risultando in un piccolo guadagno in termini di prestazioni.

Ma la soluzione più completa e più leggibile in termini di prestazioni sembra essere 05 usando un'istruzione condizionale o 04 con il quantificatore possesivo. Penso che le soluzioni Perl dovrebbero essere ancora più veloci e più facilmente leggibili.

So che è possibile abbinare una parola e poi invertire le partite usando altri strumenti (es. grep -v ). Tuttavia, mi piacerebbe sapere se è possibile abbinare linee che non contengono una parola specifica (es. Hede) usando un'espressione regolare.

Ingresso:

hoho
hihi
haha
hede

Codice:

grep "<Regex for 'doesn't contain hede'>" input

Output desiderato:

hoho
hihi
haha

Come utilizzare i verbi di controllo backtracking di PCRE per abbinare una linea che non contiene una parola

Ecco un metodo che non ho mai visto prima:

/.*hede(*COMMIT)^|/

Come funziona

In primo luogo, cerca di trovare "hede" da qualche parte nella linea. Se riesce, a questo punto, (*COMMIT)dice al motore di non solo non tornare indietro nel caso di un guasto, ma anche di non tentare ulteriori corrispondenze in quel caso. Quindi, cerchiamo di abbinare qualcosa che non può assolutamente coincidere (in questo caso, ^).

Se una linea non contiene "hede", la seconda alternativa, un subpattern vuoto, corrisponde correttamente alla stringa dell'oggetto.

Questo metodo non è più efficiente di un lookahead negativo, ma ho pensato di buttarlo qui nel caso qualcuno lo trovasse elegante e trovasse un utilizzo per altre applicazioni più interessanti.


Dall'introduzione di Ruby-2.4.1, possiamo utilizzare il nuovo Operatore assente nelle espressioni regolari di Ruby

dal doc ufficiale

(?~abc) matches: "", "ab", "aab", "cccc", etc.
It doesn't match: "abc", "aabc", "ccccabc", etc.

Quindi, nel tuo caso ^(?~hede)$ fa il lavoro per te

2.4.1 :016 > ["hoho", "hihi", "haha", "hede"].select{|s| /^(?~hede)$/.match(s)}
 => ["hoho", "hihi", "haha"]

Di cui sopra (?:(?!hede).)* È eccezionale perché può essere ancorato.

^(?:(?!hede).)*$               # A line without hede

foo(?:(?!hede).)*bar           # foo followed by bar, without hede between them

Ma quanto segue sarebbe sufficiente in questo caso:

^(?!.*hede)                    # A line without hede

Questa semplificazione è pronta per aggiungere le clausole "AND":

^(?!.*hede)(?=.*foo)(?=.*bar)   # A line with foo and bar, but without hede
^(?!.*hede)(?=.*foo).*bar       # Same

Ecco come lo farei:

^[^h]*(h(?!ede)[^h]*)*$

Preciso e più efficiente rispetto alle altre risposte. Implementa la tecnica di efficienza "srotolamento-il-loop" di Friedl e richiede un backtracking molto inferiore.


FWIW, poiché i linguaggi regolari (ovvero i linguaggi razionali) sono chiusi in complemento, è sempre possibile trovare un'espressione regolare (alias espressione razionale) che nega un'altra espressione. Ma non molti strumenti implementano questo.

Vcsn supporta questo operatore (che denota {c} , postfix).

Per prima cosa definisci il tipo delle tue espressioni: le etichette sono lettere ( lal_char ) per selezionare da a a z per esempio (definire l'alfabeto quando si lavora con la complementazione è, ovviamente, molto importante), e il "valore" calcolato per ogni parola è solo un booleano: true la parola è accettata, false , rifiutata.

In Python:

In [5]: import vcsn
        c = vcsn.context('lal_char(a-z), b')
        c
Out[5]: {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z} → 𝔹

quindi inserisci la tua espressione:

In [6]: e = c.expression('(hede){c}'); e
Out[6]: (hede)^c

converti questa espressione in un automa:

In [7]: a = e.automaton(); a

infine, converti questo automa in una semplice espressione.

In [8]: print(a.expression())
        \e+h(\e+e(\e+d))+([^h]+h([^e]+e([^d]+d([^e]+e[^]))))[^]*

dove + è solitamente indicato | , \e indica la parola vuota e [^] solito è scritta . (qualsiasi carattere). Quindi, con un po 'di riscrittura ()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).* .

Puoi vedere questo esempio e provare Vcsn online there .


La nozione secondo cui la regex non supporta la corrispondenza inversa non è completamente vera. È possibile simulare questo comportamento utilizzando look-around negativi:

^((?!hede).)*$

L'espressione regolare sopra corrisponderà a qualsiasi stringa o linea senza un'interruzione di riga, non contenente la stringa (secondaria) 'hede'. Come accennato, non si tratta di un'espressione regolare "buona" in (o dovrebbe fare), ma è comunque possibile.

E se hai bisogno di abbinare anche i caratteri di interruzione di riga, usa il modificatore DOT-ALL (il trailing s nel seguente schema):

/^((?!hede).)*$/s

o usarlo in linea:

/(?s)^((?!hede).)*$/

(dove i /.../ sono i delimitatori regex, cioè non fanno parte del pattern)

Se il modificatore DOT-ALL non è disponibile, puoi simulare lo stesso comportamento con la classe di caratteri [\s\S] :

/^((?!hede)[\s\S])*$/

Spiegazione

Una stringa è solo una lista di n caratteri. Prima e dopo ogni carattere, c'è una stringa vuota. Quindi una lista di n caratteri avrà n+1 stringhe vuote. Considera la stringa "ABhedeCD" :

    ┌──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┐
S = │e1│ A │e2│ B │e3│ h │e4│ e │e5│ d │e6│ e │e7│ C │e8│ D │e9│
    └──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┘

index    0      1      2      3      4      5      6      7

dove le e sono le corde vuote. La regex (?!hede). guarda avanti per vedere se non c'è sottostringa "hede" da vedere, e se questo è il caso (quindi qualcos'altro è visto), allora il . (punto) corrisponderà a qualsiasi carattere tranne un'interruzione di riga. Gli sguardi sono anche chiamati asserzioni a larghezza zero perché non consumano alcun carattere. Asseriscono / convalidano solo qualcosa.

Quindi, nel mio esempio, ogni stringa vuota viene prima convalidata per vedere se non c'è "hede" in anticipo, prima che un personaggio venga consumato dal . (punto). La regex (?!hede). lo farà solo una volta, quindi è racchiuso in un gruppo e ripetuto zero o più volte: ((?!hede).)* . Infine, l'inizio e la fine dell'input sono ancorati per assicurarsi che l'intero input sia consumato: ^((?!hede).)*$

Come puoi vedere, l'input "ABhedeCD" fallirà perché su e3 , la regex (?!hede) fallisce (c'è "hede" avanti!).


Le risposte date sono perfette, solo un punto accademico:

Espressioni regolari nel senso delle scienze informatiche teoriche NON SONO ABLE fare così. Per loro doveva sembrare qualcosa del genere:

^([^h].*$)|(h([^e].*$|$))|(he([^h].*$|$))|(heh([^e].*$|$))|(hehe.+$) 

Questo fa solo una partita COMPLETA. Farlo per sub-partite sarebbe anche più imbarazzante.


Poiché nessun altro ha dato una risposta diretta alla domanda che è stata posta , lo farò.

La risposta è che con POSIX grep è impossibile soddisfare letteralmente questa richiesta:

grep "Regex for doesn't contain hede" Input

Il motivo è che POSIX grep è richiesto solo per funzionare con Basic Regular Expressions , che non sono semplicemente abbastanza potenti per svolgere tale compito (non sono in grado di analizzare le lingue regolari, a causa della mancanza di alternanza e raggruppamento).

Tuttavia, GNU grep implementa le estensioni che lo consentono. In particolare, \| è l'operatore di alternanza nell'implementazione di BRE di GNU e \( e \) sono gli operatori di raggruppamento. Se il tuo motore di espressioni regolari supporta l'alternanza, le espressioni negative di parentesi, il raggruppamento e la stella di Kleene ed è in grado di ancorare all'inizio e alla fine della stringa, questo è tutto ciò che ti serve per questo approccio.

Con GNU grep , sarebbe qualcosa di simile:

grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" Input

(trovato con Grail e alcune ottimizzazioni fatte a mano).

Puoi anche utilizzare uno strumento che implementa le espressioni regolari estese , come egrep , per eliminare le barre inverse:

egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" Input

Ecco uno script per testarlo (nota che genera un file testinput.txt nella directory corrente):

#!/bin/bash
REGEX="^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$"

# First four lines as in OP's testcase.
cat > testinput.txt <<EOF
hoho
hihi
haha
hede

h
he
ah
head
ahead
ahed
aheda
ahede
hhede
hehede
hedhede
hehehehehehedehehe
hedecidedthat
EOF
diff -s -u <(grep -v hede testinput.txt) <(grep "$REGEX" testinput.txt)

Nel mio sistema stampa:

Files /dev/fd/63 and /dev/fd/62 are identical

come previsto.

Per coloro che sono interessati ai dettagli, la tecnica impiegata è convertire l'espressione regolare che corrisponde alla parola in un automa finito, quindi invertire l'automa cambiando ogni stato di accettazione in non accettazione e viceversa, e quindi convertire l'AF risultante in un'espressione regolare.

Infine, come tutti hanno notato, se il tuo motore di espressioni regolari supporta il lookahead negativo, questo semplifica molto il compito. Ad esempio, con GNU grep:

grep -P '^((?!hede).)*$' Input

Aggiornamento: Recentemente ho trovato l'eccellente libreria FormalTheory Kendall Hopkins, scritta in PHP, che fornisce una funzionalità simile a Grail.Usandolo, e un semplice programma scritto da me stesso, sono stato in grado di scrivere un generatore online di espressioni regolari negative dato una frase di input (solo caratteri alfanumerici e di spazio attualmente supportati): http://www.formauri.es/personal/pgimeno/misc/non-match-regex/

Per le hedeuscite:

^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$

che è equivalente a quanto sopra.


Se vuoi abbinare un personaggio per negare una parola simile a negare la classe del personaggio:

Ad esempio, una stringa:

<?
$str="aaa        bbb4      aaa     bbb7";
?>

Non usare:

<?
preg_match('/aaa[^bbb]+?bbb7/s', $str, $matches);
?>

Uso:

<?
preg_match('/aaa(?:(?!bbb).)+?bbb7/s', $str, $matches);
?>

Avviso "(?!bbb)." non è né lookbehind né lookahead, è lookcurrent, ad esempio:

"(?=abc)abcde", "(?!abc)abcde"

Si noti che la soluzione per non inizia con "hede" :

^(?!hede).*$

è generalmente molto più efficiente della soluzione per non contenere "hede" :

^((?!hede).)*$

Il primo controlla "hede" solo alla prima posizione della stringa di input, piuttosto che in ogni posizione.


con questo, si evita di testare un lookahead in ogni posizione:

/^(?:[^h]+|h++(?!ede))*+$/

equivalente a (per .net):

^(?>(?:[^h]+|h+(?!ede))*)$

Vecchia risposta:

/^(?>[^h]+|h+(?!ede))*$/

Risposta:

^((?!hede).)*$

Spiegazione:

^ l'inizio della stringa, ( raggruppa e cattura in \ 1 (0 o più volte (corrispondente alla maggior quantità possibile)),
(?! guarda avanti per vedere se non c'è,

hede tua corda,

) fine del look-ahead,. qualsiasi carattere tranne \ n,
)* fine di \ 1 (Nota: poiché stai usando un quantificatore su questa cattura, solo l'ULTIMA ripetizione del modello catturato verrà memorizzata in \ 1)
$ prima di un'opzione \ n e la fine della stringa


Tramite verbo PCRE (*SKIP)(*F)

^hede$(*SKIP)(*F)|^.*$

Questo salta completamente la linea che contiene l'esatta stringa hede e corrisponde a tutte le righe rimanenti.

DEMO

Esecuzione delle parti:

Consideriamo la regex di cui sopra suddividendola in due parti.

  1. Parte prima del | simbolo. La parte non dovrebbe essere abbinata .

    ^hede$(*SKIP)(*F)
    
  2. Parte dopo il | simbolo. La parte dovrebbe essere abbinata .

    ^.*$
    

PARTE 1

Il motore Regex inizierà la sua esecuzione dalla prima parte.

^hede$(*SKIP)(*F)

Spiegazione:

  • ^ Afferma che siamo all'inizio
  • hede Corrisponde alla stringa hede
  • $ Afferma che siamo alla fine della linea.

Quindi la linea che contiene la stringa hede sarebbe abbinata. Una volta che il motore regex vede il seguente comando (*SKIP)(*F) ( Nota: è possibile scrivere (*F) come (*FAIL) ), salta e fa fallire la corrispondenza. | chiamata alterazione o operatore logico OR aggiunto accanto al verbo PCRE che inturn corrisponde a tutti i limiti esistenti tra ogni singolo carattere su tutte le linee tranne la riga contiene la stringa esatta hede . Guarda la demo here . Cioè, cerca di far corrispondere i caratteri della stringa rimanente. Ora la regex nella seconda parte verrebbe eseguita.

PARTE 2

^.*$

Spiegazione:

  • ^ Afferma che siamo all'inizio cioè, corrisponde a tutte le partenze di linea tranne quella nella linea hede . Guarda la demo here .
  • .* Nella modalità multilinea,. corrisponderebbe a qualsiasi carattere tranne caratteri di ritorno a capo o a capo. E * ripeterebbe il carattere precedente zero o più volte. Quindi .* Corrisponderebbe all'intera linea. Guarda la demo here .

    Ehi, perché hai aggiunto. * Invece di. +?

    Perché .* Corrisponderebbe a una riga vuota ma .+ non corrisponderà a uno spazio vuoto. Vogliamo abbinare tutte le linee tranne hede , potrebbe esserci una possibilità di righe vuote anche nell'input. quindi è necessario utilizzare .* invece di .+ . .+ ripeterebbe il carattere precedente una o più volte. Vedi .* Corrisponde a una riga vuota here .

  • $ estremità dell'ancora di linea non è necessaria qui.


Non capisco la necessità di regex complesse o anche lookahead qui:

/hede|^(.*)$/gm

Non inserire in un gruppo di cattura ciò che non vuoi, ma usane uno per tutto il resto. Questo corrisponderà a tutte le linee che non contengono "hede".


Con ConyEdit , è possibile utilizzare la riga di comando cc.gl !/hede/per ottenere linee che non contengono la corrispondenza regex o utilizzare la riga di comando cc.dl /hede/per eliminare le righe che contengono la corrispondenza delle espressioni regolari . Hanno lo stesso risultato.


Il linguaggio TXR supporta negazione regex.

$ txr -c '@(repeat)
@{nothede /~hede/}
@(do (put-line nothede))
@(end)'  Input

Un esempio più complicato: confronta tutte le linee che iniziano con ae finiscono z, ma non contengono la sottostringa hede:

$ txr -c '@(repeat)
@{nothede /a.*z&~.*hede.*/}
@(do (put-line nothede))
@(end)' -
az         <- echoed
az
abcz       <- echoed
abcz
abhederz   <- not echoed; contains hede
ahedez     <- not echoed; contains hede
ace        <- not echoed; does not end in z
ahedz      <- echoed
ahedz

La negazione del regex non è particolarmente utile da sola, ma quando si ha anche l'intersezione, le cose diventano interessanti, dal momento che si ha un set completo di operazioni booleane: è possibile esprimere "l'insieme che corrisponde a questo, eccetto per le cose che corrispondono".


Potrebbe essere più gestibile con due espressioni regolari nel codice, una per eseguire la prima corrispondenza e, se corrisponde all'esecuzione della seconda espressione regolare per verificare i casi anomali che si desidera bloccare ad esempio, ^.*(hede).*disporre della logica appropriata nel codice.

OK, ammetto che questa non è davvero una risposta alla domanda postata e potrebbe anche usare un po 'più di elaborazione rispetto a una singola regex. Ma per gli sviluppatori che sono venuti qui in cerca di una soluzione rapida di emergenza per un caso anomalo, questa soluzione non deve essere trascurata.





regex-group