Abbina due stringhe in una riga con grep


218

Sto cercando di utilizzare grepper abbinare le linee che contengono due stringhe diverse. Ho provato quanto segue ma questo corrisponde a righe che contengono sia string1 o string2 che non è quello che voglio.

grep 'string1\|string2' filename

Quindi, come posso abbinare grepsolo le linee che contengono entrambe le stringhe ?


Risposte:


189

Puoi usare grep 'string1' filename | grep 'string2'

O, grep 'string1.*string2\|string2.*string1' filename


5
@AlexanderN in effetti non riesco a farlo funzionare con multilinea, è così strano che è stato accettato ..
Aquarius Power

1
Non era una domanda multilinea. Se fosse multilinea, grep -P supporta la regex in stile Perl ...
Scott Prive,

20
Funziona solo quando 'stringa1' E 'stringa2' si trovano sulla stessa riga. Se vuoi trovare linee con 'string1' o 'string2', vedi la risposta di user45949.
lifeson106,

10
la prima opzione: il piping di un grep in un secondo NON produce un risultato OR ma produce un risultato AND.
Masukomi,

1
Ho usatogrep -e "string1" -e "string2"
Ravi Dhoriya ツ

198

Penso che questo sia quello che stavi cercando:

grep -E "string1|string2" filename

Penso che le risposte in questo modo:

grep 'string1.*string2\|string2.*string1' filename

corrisponde solo al caso in cui entrambi sono presenti, non l'uno o l'altro o entrambi.


14
non grep -e "string1" -e "string2" filenamefarebbe lo stesso?
Janosdivenyi,

25
ecco come grep per string1 o string2. la domanda afferma chiaramente che stanno cercando string1 AND string2.
Orion Elenzil,

9
Abbastanza sicuro che la domanda sia abbastanza precisa:How do I match lines that contains *both* strings?
r0estir0bbe,

Può stampare con la stessa linea?
凡 凡

1
Perché questa risposta è ancora qui? NON è una risposta alla domanda.
Prometeo

26

Per cercare file contenenti tutte le parole in qualsiasi ordine e ovunque:

grep -ril \'action\' | xargs grep -il \'model\' | xargs grep -il \'view_type\'

Il primo grep dà il via a una ricerca ricorsiva ( r), ignorando case ( i) e elencando (stampando) il nome dei file che corrispondono ( l) per un termine ( 'action'con le virgolette singole) che si verificano in qualsiasi punto del file.

I greps successivi cercano gli altri termini, mantenendo l'insensibilità del caso e elencando i file corrispondenti.

L'elenco finale dei file che otterrai saranno quelli che contengono questi termini, in qualsiasi ordine in qualsiasi parte del file.


2
Concordato! Noterò solo che dovevo dare a xargs un "-d '\ n" per gestire i nomi dei file con spazi. Questo ha funzionato per me su Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Tommy Harris,

16

Se hai un grepcon -Pun'opzione per un perlregex limitato , puoi usare

grep -P '(?=.*string1)(?=.*string2)'

che ha il vantaggio di lavorare con stringhe sovrapposte. È un po 'più semplice usare perlas grep, perché puoi specificare la logica e più direttamente:

perl -ne 'print if /string1/ && /string2/'

1
Migliore risposta. Shell è molto semplice e veloce, ma una volta che lo schema diventa complesso dovresti usare Python o Perl (o Awk). Non battere la testa contro il muro cercando di dimostrare che può essere fatto in guscio puro (qualunque cosa significhi in questi giorni). Un promemoria gente, questi strumenti possono essere utilizzati nella sintassi "one liner" che sono incorporati in uno script di shell esistente.
Scott Prive,

12

Il tuo metodo era quasi buono, mancava solo il -w

grep -w 'string1\|string2' filename

1
Almeno su OS-X e FreeBSD funziona! La mia ipotesi è che tu abbia qualcos'altro (che l'OP non ha definito - spero che tu non abbia votato in negativo una risposta corretta a molti utenti tranne te).
Leo,

Sono su OS-X. Forse non lo sto facendo correttamente? Dai un'occhiata a quello che ho fatto: i.imgur.com/PFVlVAG.png
Ariel,

1
Dispari. Mi aspettavo che la differenza fosse nel non eseguire il grepping nel file, ma, se installo il mio metodo con il tuo ls, ottengo il risultato che non lo fai: imgur.com/8eTt3Ak.png - Entrambi su entrambi OS-X 10.9.5 ( "grep (BSD grep) 2.5.1-FreeBSD") e FreeBSD 10 ("grep (GNU grep) 2.5.1-FreeBSD"). Sono curioso di sapere qual è il tuo grep -V.
Leo,

1
I tuoi esempi funzionano per me: i.imgur.com/K8LM69O.png Quindi la differenza è che questo metodo non raccoglie sottostringhe, devono essere stringhe complete da sole. Suppongo che dovrai costruire regexps all'interno del grep per cercare sottostringhe. Qualcosa del genere:grep -w 'regexp1\|regexp2' filename
Ariel,

2
OP mostra un esempio abbinando string1 o string2 e chiede come abbinare le righe che contengono entrambe le stringhe. Questo esempio produce ancora OR.
gustafbstrom,

7

L' |operatore in un'espressione regolare significa o. Vale a dire che stringa1 o stringa2 corrisponderanno. Potresti fare:

grep 'string1' filename | grep 'string2'

che invierà i risultati dal primo comando al secondo grep. Questo dovrebbe darti solo linee che corrispondono ad entrambi.


1
Le tue affermazioni sono vere, ma non rispondono alla domanda OP
Ben Wheeler,

Questo risponde alla domanda ed è proprio così che la maggior parte delle persone la scrive.
Peter K,

7

Potresti provare qualcosa del genere:

(pattern1.*pattern2|pattern2.*pattern1)

4

E come le persone hanno suggerito perl e python e script di shell contorti, ecco un semplice approccio awk :

awk '/string1/ && /string2/' filename

Dopo aver guardato i commenti alla risposta accettata: no, questo non fa multilinea; ma poi non è nemmeno quello che l'autore della domanda ha chiesto.


3

Non provare a usare grep per questo, usa invece awk. Per abbinare 2 regexps R1 e R2 in grep penseresti che sarebbe:

grep 'R1.*R2|R2.*R1'

mentre in awk sarebbe:

awk '/R1/ && /R2/'

ma cosa succede se si R2sovrappone o è un sottoinsieme di R1? Quel comando grep semplicemente non avrebbe funzionato mentre il comando awk avrebbe funzionato. Diciamo che vuoi trovare linee che contengono thee heat:

$ echo 'theatre' | grep 'the.*heat|heat.*the'
$ echo 'theatre' | awk '/the/ && /heat/'
theatre

Dovresti usare 2 greps e una pipe per quello:

$ echo 'theatre' | grep 'the' | grep 'heat'
theatre

e ovviamente se avevi richiesto loro di essere separati, puoi sempre scrivere in awk lo stesso regexp usato in grep e ci sono soluzioni awk alternative che non implicano la ripetizione dei regexps in ogni possibile sequenza.

Mettendolo da parte, cosa succede se si desidera estendere la soluzione per abbinare 3 regexps R1, R2 e R3. In grep sarebbe una di queste povere scelte:

grep 'R1.*R2.*R3|R1.*R3.*R2|R2.*R1.*R3|R2.*R3.*R1|R3.*R1.*R2|R3.*R2.*R1' file
grep R1 file | grep R2 | grep R3

mentre in awk sarebbe il conciso, ovvio, semplice, efficiente:

awk '/R1/ && /R2/ && /R3/'

Ora, cosa succederebbe se davvero volessi abbinare le stringhe letterali S1 e S2 invece di regexps R1 e R2? Semplicemente non puoi farlo in una chiamata a grep, devi scrivere il codice per sfuggire a tutti i metachar RE prima di chiamare grep:

S1=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R1')
S2=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R2')
grep 'S1.*S2|S2.*S1'

o ancora usa 2 greps e una pipa:

grep -F 'S1' file | grep -F 'S2'

che di nuovo sono scelte sbagliate mentre con awk usi semplicemente un operatore stringa invece dell'operatore regexp:

awk 'index($0,S1) && index($0.S2)'

E se volessi abbinare 2 regexps in un paragrafo anziché in una riga? Non può essere fatto in grep, banale in awk:

awk -v RS='' '/R1/ && /R2/'

Che ne dici di un intero file? Di nuovo non può essere fatto in grep e banale in awk (questa volta sto usando GNU awk per RS ​​multi-char per concisione, ma non c'è molto più codice in qualsiasi awk o puoi scegliere un control-char che sai che non lo farà essere nell'input affinché RS faccia lo stesso):

awk -v RS='^$' '/R1/ && /R2/'

Quindi - se vuoi trovare più regexps o stringhe in una riga, in un paragrafo o in un file, non usare grep, usa awk.


Non fa awk '/R1/ && /R2/'distinzione tra maiuscole e minuscole?
Prometeo

@Hashim - no. Per renderlo insensibile alle maiuscole e minuscole con GNU Awk che faresti awk -v IGNORECASE=1 '/R1/ && /R2/'e con qualsiasi Awkawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ed Morton,

3
grep string1\|string2 FILENAME 

GNU grep versione 3.1


2

Linee trovate che iniziano solo con 6 spazi e finiscono con:

 cat my_file.txt | grep
 -e '^      .*(\.c$|\.cpp$|\.h$|\.log$|\.out$)' # .c or .cpp or .h or .log or .out
 -e '^      .*[0-9]\{5,9\}$' # numers between 5 and 9 digist
 > nolog.txt

2

Diciamo che dobbiamo trovare il conteggio di più parole in un file testfile. Ci sono due modi per farlo

1) Utilizzare il comando grep con il modello di corrispondenza regex

grep -c '\<\(DOG\|CAT\)\>' testfile

2) Utilizzare il comando egrep

egrep -c 'DOG|CAT' testfile 

Con egrep non devi preoccuparti dell'espressione e semplicemente separare le parole da un separatore di tubi.


2

git grep

Ecco la sintassi usando git grepcon più pattern:

git grep --all-match --no-index -l -e string1 -e string2 -e string3 file

È inoltre possibile combinare motivi con espressioni booleane come --and, --ore--not .

Cerca man git-grepaiuto.


--all-match Quando si danno espressioni di pattern multipli, viene specificato questo flag limitare la corrispondenza ai file che hanno linee che corrispondono a tutte .

--no-index Cerca i file nella directory corrente che non è gestita da Git.

-l/ --files-with-matches/--name-only Mostra solo i nomi dei file.

-eIl prossimo parametro è il modello. L'impostazione predefinita è utilizzare regexp di base.

Altri parametri da considerare:

--threads Numero di thread di lavoro grep da utilizzare.

-q/ --quiet/--silent Non emette righe corrispondenti; esce con lo stato 0 quando c'è una corrispondenza.

Per cambiare il tipo di motivo, puoi anche usare -G/ --basic-regexp(impostazione predefinita), -F/ --fixed-strings, -E/ --extended-regexp, -P/--perl-regexp ,-f file e altri.

Relazionato:

Per il funzionamento OR , vedere:


2
Ho sempre pensato che "git grep" può essere eseguito solo all'interno di un repository git. Non ero a conoscenza dell'opzione --no-index. Grazie per segnalarlo!
Kamaraju Kusumanchi,

1

Inserire le stringhe che si desidera eseguire il grep in un file

echo who    > find.txt
echo Roger >> find.txt
echo [44][0-9]{9,} >> find.txt

Quindi cerca usando -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 

1
grep '(string1.*string2 | string2.*string1)' filename

otterrà la linea con string1 e string2 in qualsiasi ordine


In che modo differisce dalle prime due risposte?
luk2302,

1
grep -i -w 'string1\|string2' filename

Funziona per la corrispondenza esatta delle parole e per la corrispondenza delle parole senza distinzione tra maiuscole e minuscole, poiché viene utilizzato -i


0

per la partita multilinea:

echo -e "test1\ntest2\ntest3" |tr -d '\n' |grep "test1.*test3"

o

echo -e "test1\ntest5\ntest3" >tst.txt
cat tst.txt |tr -d '\n' |grep "test1.*test3\|test3.*test1"

dobbiamo solo rimuovere il carattere newline e funziona!


0

Dovresti avere grepcosì:

$ grep 'string1' file | grep 'string2'

1
Questo esegue un AND logico. OP vuole un OR logico.
Ben Wheeler,

1
@BenWheeler: Dalla domanda: "Quindi come posso abbinare a grep solo le linee che contengono entrambe le stringhe?"
Erik I il

0

Mi capita spesso di riscontrare lo stesso problema del tuo e ho appena scritto un pezzo di sceneggiatura:

function m() { # m means 'multi pattern grep'

    function _usage() {
    echo "usage: COMMAND [-inH] -p<pattern1> -p<pattern2> <filename>"
    echo "-i : ignore case"
    echo "-n : show line number"
    echo "-H : show filename"
    echo "-h : show header"
    echo "-p : specify pattern"
    }

    declare -a patterns
    # it is important to declare OPTIND as local
    local ignorecase_flag  filename linum header_flag colon result OPTIND

    while getopts "iHhnp:" opt; do
    case $opt in
        i)
        ignorecase_flag=true ;;
        H)
        filename="FILENAME," ;;
        n)
        linum="NR," ;;
        p)
        patterns+=( "$OPTARG" ) ;;
        h)
        header_flag=true ;;
        \?)
        _usage
        return ;;
    esac
    done

    if [[ -n $filename || -n $linum ]]; then
    colon="\":\","
    fi

    shift $(( $OPTIND - 1 ))

    if [[ $ignorecase_flag == true ]]; then
    for s in "${patterns[@]}"; do
            result+=" && s~/${s,,}/"
    done
    result=${result# && }
    result="{s=tolower(\$0)} $result"
    else
    for s in "${patterns[@]}"; do
            result="$result && /$s/"
    done
    result=${result# && }
    fi

    result+=" { print "$filename$linum$colon"\$0 }"

    if [[ ! -t 0 ]]; then       # pipe case
    cat - | awk "${result}"
    else
    for f in "$@"; do
        [[ $header_flag == true ]] && echo "########## $f ##########"
        awk "${result}" $f
    done
    fi
}

Uso:

echo "a b c" | m -p A 
echo "a b c" | m -i -p A # a b c

Puoi metterlo in .bashrc se vuoi.


0

Quando entrambe le stringhe sono in sequenza, inserire un modello tra il grepcomando:

$ grep -E "string1(?.*)string2" file

Esempio se le seguenti righe sono contenute in un file denominato Dockerfile:

FROM python:3.8 as build-python
FROM python:3.8-slim

Per ottenere la riga che contiene le stringhe: FROM pythone as build-pythonquindi utilizzare:

$ grep -E "FROM python:(?.*) as build-python" Dockerfile

Quindi l'output mostrerà solo la linea che contiene entrambe le stringhe :

FROM python:3.8 as build-python

-2

ripgrep

Ecco l'esempio usando rg:

rg -N '(?P<p1>.*string1.*)(?P<p2>.*string2.*)' file.txt

È uno degli strumenti più veloci per il grepping, dato che è costruito sul motore regex di Rust che utilizza automi finiti, SIMD e ottimizzazioni letterali aggressive per rendere la ricerca molto veloce.

Usalo, specialmente quando lavori con dati di grandi dimensioni.

Vedere anche la richiesta di funzionalità correlate su GH-875 .


1
Questa risposta non è del tutto giusta. I gruppi di acquisizione nominati non sono necessari e questo non gestisce il caso quando string2appare prima string1. La soluzione più semplice a questo problema è rg string1 file.txt | rg string2.
BurntSushi5,
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.