Come posso grep per le righe che contengono una delle due parole, ma non entrambe?


25

Sto cercando di usare grepper mostrare solo le righe contenenti una delle due parole, se solo una di esse appare nella riga, ma non se si trovano nella stessa riga.

Finora ci ho provato grep pattern1 | grep pattern2 | ...ma non ho ottenuto il risultato che mi aspettavo.


(1) Parli di "parole" e "schemi". Cos'è questo? Parole ordinarie come "veloce", "marrone" e "volpe", o espressioni regolari come [a-z][a-z0-9]\(,7\}\(\.[a-z0-9]\{,3\}\)+? (2) Cosa succede se una delle parole / motivi appare più di una volta in una riga (e l'altra non appare)? È equivalente alla parola che appare una volta o conta come ricorrenze multiple?
G-Man dice "Reinstate Monica" il

Risposte:


59

Uno strumento diverso da quello grepè la strada da percorrere.

Usando perl, ad esempio, il comando sarebbe:

perl -ne 'print if /pattern1/ xor /pattern2/'

perl -neesegue il comando dato su ciascuna riga di stdin, che in questo caso stampa la riga se corrisponde /pattern1/ xor /pattern2/, o in altre parole corrisponde a un modello ma non all'altro (esclusivo o).

Questo funziona per il modello in entrambi gli ordini e dovrebbe avere prestazioni migliori rispetto a più invocazioni di grep, ed è anche meno digitante.

O, ancora più breve, con awk:

awk 'xor(/pattern1/,/pattern2/)'

o per le versioni di awk che non hanno xor:

awk '/pattern1/+/pattern2/==1`

4
Bello - Awk è xordisponibile solo in GNU Awk?
steeldriver

9
@steeldriver Penso che sia solo GNU, sì. O almeno manca nelle versioni precedenti. È possibile sostituirlo con /pattern1/+/pattern2/==1ir xormancante.
Chris

4
@JimL. Potresti mettere i confini delle parole ( \b) negli schemi stessi, cioè \bword\b.
wjandrea,

4
@vikingsteve Se si desidera utilizzare grep in modo specifico, ci sono molte altre risposte qui. Ma per le persone che vogliono solo fare il lavoro, è bene sapere che ci sono altri strumenti che possono fare tutto ciò che Grep fa, ma sempre più facilmente.
Chris

3
@vikingsteve Suppongo fortemente che la domanda di una soluzione grep sia una specie di problema XY
Hagen von Eitzen

30

Con GNU grep, è possibile passare entrambe le parole a, grepquindi rimuovere le righe contenenti entrambi i motivi.

$ cat testfile.txt
abc
def
abc def
abc 123 def
1234
5678
1234 def abc
def abc

$ grep -w -e 'abc' -e 'def' testfile.txt | grep -v -e 'abc.*def' -e 'def.*abc'
abc
def

16

Prova con egrep

egrep  'pattern1|pattern2' file | grep -v -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'

3
può anche essere scritto comegrep -e foo -e bar | grep -v -e 'foo.*bar' -e 'bar.*foo'
glenn jackman il

8
Inoltre, nota dalla pagina man di grep: Direct invocation as either egrep or fgrep is deprecated- preferiscigrep -E
glenn jackman il

Questo non è nel mio sistema operativo @glennjackman
Grump

1
@Grump davvero? Che sistema operativo è quello? Anche POSIX menziona che grep dovrebbe avere -fe le -eopzioni, anche se più vecchie egrepe fgrepcontinueranno a essere supportate per un po '.
terdon

1
@terdon, POSIX non specifica il percorso delle utility POSIX. Anche in questo caso, c'è, lo standard grep(che supporta -F, -E, -e, -fcome richiede POSIX) è in /usr/xpg4/bin. Le utility in /binsono antiquate.
Stéphane Chazelas,

12

Con grepimplementazioni che supportano espressioni regolari simili a perl (like pcregrepo GNU o ast-open grep -P), puoi farlo in unagrep chiamata con:

grep -P '^(?=.*pat1)(?!.*pat2)|^(?=.*pat2)(?!.*pat1)'

Cioè trova le linee che corrispondono pat1ma non pat2, o pat2nopat1 .

(?=...)e (?!...)sono rispettivamente operatori del futuro e del futuro. Quindi tecnicamente, quanto sopra cerca l'inizio del soggetto ( ^) a condizione che sia seguito da .*pat1e non seguito da .*pat2, o lo stesso conpat1 e pat2invertito.

Questo non è ottimale per le linee che contengono entrambi i modelli come verrebbero ricercate due volte. È possibile invece utilizzare operatori perl più avanzati come:

grep -P '^(?=.*pat1|())(?(1)(?=.*pat2)|(?!.*pat2))'

(?(1)yespattern|nopattern)corrisponde a yespatternse il gruppo di acquisizione della 1st (vuoto ()sopra) corrisponde e in caso nopatterncontrario. Se ()corrisponde, significa pat1che non corrisponde, quindi cerchiamo pat2(sguardo positivo in avanti) e cerchiamo non pat2 altrimenti (negativo sguardo al futuro).

Con sed, potresti scriverlo:

sed -ne '/pat1/{/pat2/!p;d;}' -e '/pat2/p'

La tua prima soluzione fallisce grep: the -P option only supports a single pattern, almeno su tutti i sistemi a cui ho accesso. +1 per la tua seconda soluzione, però.
Chris

1
@ Chris, hai ragione. Questa sembra essere una limitazione specifica di GNU grep. pcregrepe ast-open grep non ha questo problema. Ho sostituito il multiplo -econ l'operatore di alternanza RE, quindi ora dovrebbe funzionare anche con GNU grep.
Stéphane Chazelas,

Sì, ora funziona bene.
Chris

3

In termini booleani, stai cercando A xor B, che può essere scritto come

(A e non B)

o

(B e non A)

Dato che la tua domanda non menziona il fatto che sei interessato all'ordine dell'output fintanto che vengono mostrate le linee corrispondenti, l'espansione booleana di A xor B è dannatamente semplice in grep:

$ cat << EOF > foo
> a b
> a
> b
> c a
> c b
> b a
> b c
> EOF
$ grep -w 'a' foo | grep -vw 'b'; grep -w 'b' foo | grep -vw 'a';
a
c a
b
c b
b c

1
Funziona, ma rimescolerà l'ordine del file.
Sparhawk,

@Sparhawk Vero, sebbene "scramble" sia una parola aspra. ;) elenca prima tutte le corrispondenze 'a', in ordine, quindi tutte le corrispondenze 'b', successivamente, in ordine. L'OP non ha espresso alcun interesse a mantenere l'ordine, mostra solo le linee. FAWK, il prossimo passo potrebbe essere sort | uniq.
Jim L.

Chiamata giusta; Sono d'accordo che la mia lingua non era accurata. Volevo dire che l'ordine originale sarebbe cambiato.
Sparhawk,

1
@Sparhawk ... E ho modificato la tua osservazione per una completa divulgazione.
Jim L.

-2

Per il seguente esempio:

# Patterns:
#    apple
#    pear

# Example line
line="a_apple_apple_pear_a"

Questo può essere fatto esclusivamente con grep -E, uniqe wc.

# Grep for regex pattern, sort as unique, and count the number of lines
result=$(grep -oE 'apple|pear' <<< $line | sort -u | wc -l)

Se grepviene compilato con le espressioni regolari Perl, puoi abbinare l'ultima occorrenza invece di dover reindirizzare a uniq:

# Grep for regex pattern and count the number of lines
result=$(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l)

Emetti il ​​risultato:

# Only one of the words exists if the result is < 2
((result > 0)) &&
   if (($result < 2)); then
      echo Only one word matched
   else
      echo Both words matched
   fi

Una fodera:

(($(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l) == 1)) && echo Only one word matched

Se non si desidera codificare il modello, è possibile automatizzare con una funzione il montaggio con un set variabile di elementi.

Questo può anche essere fatto in modo nativo in Bash come una funzione senza pipe o processi aggiuntivi, ma sarebbe più coinvolto ed è probabilmente al di fuori dell'ambito della tua domanda.


(1) Mi chiedevo quando qualcuno avrebbe dato una risposta usando le espressioni regolari del Perl. Se ti sei concentrato su quella parte del tuo post e hai spiegato come ha funzionato, questa potrebbe essere una buona risposta. (2) Ma temo che il resto non sia così buono. La domanda dice "mostra solo le righe contenenti una delle due parole" (enfasi aggiunta). Se si suppone che l'output sia costituito da righe , è logico che anche l'input debba essere composto da più righe.   Ma il tuo approccio funziona solo quando si guarda a una sola riga. ... (proseguendo)
G-Man dice "Reinstate Monica" il

(Continua) ... Ad esempio, se l'input contiene le righe Big apple\ne pear-shaped\n, l'output dovrebbe contenere entrambe le righe. La tua soluzione otterrebbe un conteggio di 2; la versione lunga riportava "Entrambe le parole abbinate" (che è una risposta alla domanda sbagliata) e la versione breve non diceva nulla. (3) Un suggerimento: usare -oqui è una pessima idea, perché nasconde le linee che contengono le corrispondenze, quindi non puoi vedere quando entrambe le parole appaiono sulla stessa linea. ... (proseguendo)
G-Man dice "Reinstate Monica" il

(Proseguendo) ... (4) In conclusione: l'uso di uniq/ sort -ue l'espressione regolare Perl per abbinare solo l'ultima occorrenza su ciascuna riga non aggiunge realmente una risposta utile a questa domanda. Ma, anche se lo facessero, sarebbe comunque una cattiva risposta perché non spieghi come contribuiscono a rispondere alla domanda. (Vedi la risposta di Stéphane Chazelas per un esempio di una buona spiegazione.)
G-Man dice "Reinstate Monica" il

L'OP afferma che volevano "mostrare solo le righe contenenti una delle due parole", il che significa che ogni riga deve essere valutata da sola. Non vedo perché pensi che questo non risponda alla domanda. Fornisci un esempio di input che ritieni fallirebbe.
Zhro

Oh, è quello che volevi dire? “Leggi l'input una riga alla volta ed esegui questi due o tre comandi per ogni riga . “? (1) Non è dolorosamente chiaro che questo è ciò che intendevi. (2) È dolorosamente inefficiente. Quattro risposte prima delle tue hanno mostrato come gestire l' intero file in pochi comandi (uno, due o quattro) e vuoi eseguire 3 ×  n comandi per n righe di input? Anche se funziona, guadagna un voto negativo per l'esecuzione inutilmente costosa. (3) A rischio di spaccare i peli, non fa ancora il lavoro di mostrare le linee appropriate.
G-Man dice "Reinstate Monica" il
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.