Ordnen Sie zwei Zeichenfolgen in einer Zeile Grep zu

218

Ich versuche, grepLinien zuzuordnen, die zwei verschiedene Zeichenfolgen enthalten. Ich habe Folgendes versucht, aber dies stimmt mit Zeilen überein, die entweder string1 oder string2 enthalten, was nicht das ist, was ich will.

grep 'string1\|string2' filename

Wie stimme ich also grepnur mit den Zeilen überein , die beide Zeichenfolgen enthalten ?

Heartsaxas
quelle
1
Siehe auch
AlikElzin-kilaka

Antworten:

189

Sie können verwenden grep 'string1' filename | grep 'string2'

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

Dheerosaurier
quelle
5
@ AlexanderN in der Tat kann ich es nicht mit Multiline arbeiten lassen, das ist so seltsam, dass es akzeptiert wurde ..
Aquarius Power
1
Es war keine mehrzeilige Frage. Wenn es mehrzeilig wäre, unterstützt grep -P Regex im Perl-Stil ...
Scott Prive
20
Funktioniert nur, wenn sich sowohl 'string1' als auch 'string2' in derselben Zeile befinden. Wenn Sie Zeilen mit 'string1' oder 'string2' suchen möchten, lesen Sie die Antwort von user45949.
lifeson106
10
Die erste Option: Wenn Sie einen Grep in einen zweiten leiten, wird KEIN ODER-Ergebnis erzeugt, sondern ein UND-Ergebnis.
Masukomi
1
Ich benutztegrep -e "string1" -e "string2"
Ravi Dhoriya
198

Ich denke, das haben Sie gesucht:

grep -E "string1|string2" filename

Ich denke, dass Antworten wie folgt:

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

Passen Sie nur den Fall an, in dem beide vorhanden sind, nicht der eine oder der andere oder beide.

user45949
quelle
14
würde nicht grep -e "string1" -e "string2" filenamedas gleiche tun?
Janosdivenyi
25
So suchen Sie nach string1 ODER string2. Die Frage besagt eindeutig, dass sie nach string1 UND string2 suchen.
Orion Elenzil
9
Ziemlich sicher, dass die Frage ziemlich genau ist:How do I match lines that contains *both* strings?
r0estir0bbe
Kann es mit derselben Zeile drucken?
27 凡
1
Warum ist diese Antwort immer noch hier? Es ist KEINE Antwort auf die Frage.
Prometheus
26

So suchen Sie nach Dateien, die alle Wörter in beliebiger Reihenfolge enthalten:

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

Der erste grep startet eine rekursive Suche ( r), ignoriert case ( i) und listet (druckt) den Namen der Dateien auf, die lfür einen Begriff ( 'action'mit einfachen Anführungszeichen) übereinstimmen ( ), der irgendwo in der Datei vorkommt.

Die nachfolgenden Greps suchen nach den anderen Begriffen, wobei die Groß- und Kleinschreibung nicht berücksichtigt wird und die übereinstimmenden Dateien aufgelistet werden.

Die endgültige Liste der Dateien, die Sie erhalten, enthält diejenigen, die diese Begriffe enthalten, in beliebiger Reihenfolge an einer beliebigen Stelle in der Datei.

Kinjal Dixit
quelle
2
Einverstanden! Ich werde nur bemerken, dass ich xargs ein "-d '\ n'" geben musste, um Dateinamen mit Leerzeichen zu behandeln. Dies funktionierte für mich unter Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Tommy Harris
16

Wenn Sie grepeine -POption für eine begrenzte perlRegex haben, können Sie diese verwenden

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

Das hat den Vorteil, mit überlappenden Strings zu arbeiten. Die Verwendung von perlas ist etwas einfacher grep, da Sie die und -Logik direkter angeben können:

perl -ne 'print if /string1/ && /string2/'
tchrist
quelle
1
Beste Antwort. Shell ist sehr einfach und schnell, aber sobald das Muster komplex wird, sollten Sie Python oder Perl (oder Awk) verwenden. Schlagen Sie Ihren Kopf nicht gegen die Wand und versuchen Sie zu beweisen, dass dies in reiner Schale möglich ist (was auch immer das heutzutage bedeutet). Zur Erinnerung, diese Tools können in der "One-Liner" -Syntax verwendet werden, die in ein vorhandenes Shell-Skript eingebettet ist.
Scott Prive
12

Ihre Methode war fast gut, nur das -w fehlte

grep -w 'string1\|string2' filename
Löwe
quelle
1
Zumindest unter OS-X und FreeBSD funktioniert es! Ich vermute, Sie haben etwas anderes vor (was das OP nicht definiert hat - ich hoffe, Sie haben vielen Benutzern außer Ihnen keine korrekte Antwort zugestimmt).
Leo
Ich bin auf OS-X. Vielleicht mache ich das nicht richtig? Schauen Sie sich an, was ich getan habe: i.imgur.com/PFVlVAG.png
Ariel
1
Seltsam. Ich habe erwartet, dass der Unterschied darin besteht, dass ich nicht in Dateien greife, aber wenn ich meine Methode mit Ihrem ls weitergebe, erhalte ich das Ergebnis, dass Sie dies nicht tun: imgur.com/8eTt3Ak.png - Beide auf beiden OS-X 10.9.5 ( "grep (BSD grep) 2.5.1-FreeBSD") und FreeBSD 10 ("grep (GNU grep) 2.5.1-FreeBSD"). Ich bin neugierig, was dein grep -Vist.
Leo
1
Ihre Beispiele funktionieren für mich: i.imgur.com/K8LM69O.png Der Unterschied besteht also darin, dass diese Methode keine Teilzeichenfolgen aufnimmt , sondern eigenständige Zeichenfolgen sein müssen. Ich denke, Sie müssen reguläre Ausdrücke innerhalb des grep erstellen, um nach Teilzeichenfolgen zu suchen. So etwas wie das:grep -w 'regexp1\|regexp2' filename
Ariel
2
OP zeigt ein Beispiel durch Abgleichen von Zeichenfolge1 oder Zeichenfolge2 und fragt, wie Zeilen abgeglichen werden sollen, die beide Zeichenfolgen enthalten. Dieses Beispiel ergibt immer noch OR.
Gustafbstrom
7

Der |Operator in einem regulären Ausdruck bedeutet oder. Das heißt, entweder string1 oder string2 stimmen überein. Du könntest es tun:

grep 'string1' filename | grep 'string2'

Dadurch werden die Ergebnisse des ersten Befehls in den zweiten grep weitergeleitet. Das sollte Ihnen nur Zeilen geben, die mit beiden übereinstimmen.

martineno
quelle
1
Ihre Aussagen sind wahr, aber beantworten Sie nicht OP-Frage
Ben Wheeler
Dies beantwortet die Frage und so schreiben es die meisten Leute.
Peter K
7

Sie könnten so etwas versuchen:

(pattern1.*pattern2|pattern2.*pattern1)
Dorn
quelle
4

Und wie die Leute Perl und Python und verschlungene Shell-Skripte vorgeschlagen haben, hier ein einfacher awk- Ansatz:

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

Nachdem Sie sich die Kommentare zur akzeptierten Antwort angesehen haben: Nein, dies ist nicht mehrzeilig. aber das hat der Autor der Frage auch nicht verlangt.

basteln
quelle
3

Versuchen Sie nicht, grep dafür zu verwenden, sondern verwenden Sie stattdessen awk. Um 2 reguläre Ausdrücke R1 und R2 in grep abzugleichen, würde man denken, es wäre:

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

während in awk wäre es:

awk '/R1/ && /R2/'

aber was ist, wenn R2sich mit oder eine Teilmenge von überschneidet R1? Dieser grep-Befehl würde einfach nicht funktionieren, während der awk-Befehl dies tun würde. Können sagen , Sie Linien finden möchten , die enthalten theund heat:

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

Dafür müssten Sie 2 Greps und eine Pipe verwenden:

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

und natürlich, wenn Sie tatsächlich verlangt hätten, dass sie getrennt sind, können Sie in awk immer den gleichen regulären Ausdruck schreiben, den Sie in grep verwendet haben, und es gibt alternative awk-Lösungen, bei denen die regulären Ausdrücke nicht in jeder möglichen Reihenfolge wiederholt werden.

Abgesehen davon, was wäre, wenn Sie Ihre Lösung auf 3 reguläre Ausdrücke R1, R2 und R3 erweitern möchten. In grep wäre das eine dieser schlechten Entscheidungen:

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

während in awk wäre es das prägnante, offensichtliche, einfache, effiziente:

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

Was wäre, wenn Sie tatsächlich die Literalzeichenfolgen S1 und S2 anstelle der regulären Ausdrücke R1 und R2 abgleichen möchten? Sie können das einfach nicht in einem Aufruf von grep tun. Sie müssen entweder Code schreiben, um alle RE-Metachare zu umgehen, bevor Sie grep aufrufen:

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

oder wieder 2 greps und eine Pfeife verwenden:

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

Dies sind wiederum schlechte Entscheidungen, während Sie mit awk einfach einen Zeichenfolgenoperator anstelle des regulären Ausdrucksoperators verwenden:

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

Was wäre, wenn Sie 2 reguläre Ausdrücke in einem Absatz und nicht in einer Zeile abgleichen möchten? Kann nicht in grep gemacht werden, trivial in awk:

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

Wie wäre es mit einer ganzen Datei? Wiederum kann es in awp nicht in grep und trivial gemacht werden (diesmal verwende ich GNU awk für Multi-Char-RS aus Gründen der Übersichtlichkeit, aber es ist nicht viel mehr Code in einem awk, oder Sie können ein Control-Char auswählen, von dem Sie wissen, dass es nicht funktioniert in der Eingabe sein, damit der RS ​​dasselbe tut):

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

Wenn Sie also mehrere reguläre Ausdrücke oder Zeichenfolgen in einer Zeile, einem Absatz oder einer Datei suchen möchten, verwenden Sie nicht grep, sondern awk.

Ed Morton
quelle
Ist die awk '/R1/ && /R2/'Groß- und Kleinschreibung nicht zu beachten?
Prometheus
@ Hashim - nein. Um es mit GNU awk unabhängig von Groß- und Kleinschreibung zu machen, würden Sie es tun awk -v IGNORECASE=1 '/R1/ && /R2/'und mit jedem awkawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ed Morton
3
grep string1\|string2 FILENAME 

GNU grep Version 3.1

Tilikoom
quelle
2

Gefundene Zeilen, die nur mit 6 Leerzeichen beginnen und enden mit:

 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
Cristian
quelle
2

Angenommen, wir müssen die Anzahl mehrerer Wörter in einer Datei-Testdatei ermitteln. Es gibt zwei Möglichkeiten

1) Verwenden Sie den Befehl grep mit dem Regex-Übereinstimmungsmuster

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

2) Verwenden Sie den Befehl egrep

egrep -c 'DOG|CAT' testfile 

Mit egrep müssen Sie sich keine Gedanken über den Ausdruck machen und nur Wörter durch ein Rohrtrennzeichen trennen.

Amit Singh
quelle
2

git grep

Hier ist die Syntax git grepmit mehreren Mustern:

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

Sie können auch Muster mit kombinieren Booleschen Ausdrücken wie --and, --orund --not.

Suchen Sie man git-grepnach Hilfe.


--all-matchWenn Sie mehrere Musterausdrücke angeben, wird dieses Flag angegeben, um die Übereinstimmung auf Dateien zu beschränken, deren Zeilen mit allen übereinstimmen .

--no-index Suchen Sie nach Dateien im aktuellen Verzeichnis, die nicht von Git verwaltet werden.

-l/ --files-with-matches/ --name-onlyNur die Namen von Dateien.

-eDer nächste Parameter ist das Muster. Standardmäßig wird der normale reguläre Ausdruck verwendet.

Weitere zu berücksichtigende Parameter:

--threads Anzahl der zu verwendenden Grep Worker-Threads.

-q/ --quiet/ --silentNicht ausgeben abgestimmt Linien; Beenden Sie mit Status 0, wenn eine Übereinstimmung vorliegt.

Um das Muster Typ zu ändern, können Sie auch -G/ --basic-regexp(Standard), -F/ --fixed-strings, -E/ --extended-regexp, -P/ --perl-regexp, -f fileund andere.

Verbunden:

Informationen zum ODER- Betrieb finden Sie unter:

Kenorb
quelle
2
Ich habe immer gedacht, dass "git grep" nur in einem Git-Repository ausgeführt werden kann. Die Option --no-index war mir nicht bekannt. Vielen Dank für den Hinweis!
Kamaraju Kusumanchi
1

Platzieren Sie die Zeichenfolgen, nach denen Sie suchen möchten, in einer Datei

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

Dann suchen Sie mit -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 
Tim Seed
quelle
1
grep '(string1.*string2 | string2.*string1)' filename

wird Zeile mit Zeichenfolge1 und Zeichenfolge2 in beliebiger Reihenfolge erhalten

James
quelle
Inwiefern unterscheidet sich das von mindestens den beiden wichtigsten Antworten?
luk2302
1
grep -i -w 'string1\|string2' filename

Dies funktioniert für exakte Wortübereinstimmung und übereinstimmende Wörter ohne Berücksichtigung der Groß- und Kleinschreibung, für die -i verwendet wird

Saurabh
quelle
0

für mehrzeiliges Spiel:

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

oder

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

Wir müssen nur das Zeilenumbruchzeichen entfernen und es funktioniert!

Wassermann-Kraft
quelle
0

Sie sollten so haben grep:

$ grep 'string1' file | grep 'string2'
Raghuram
quelle
1
Dies führt ein logisches UND aus. OP möchte ein logisches ODER.
Ben Wheeler
1
@ BenWheeler: Aus der Frage: "Wie kann ich mit grep nur die Zeilen abgleichen, die beide Zeichenfolgen enthalten?"
Erik I
0

Ich habe oft das gleiche Problem wie Sie und habe gerade ein Skript geschrieben:

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
}

Verwendung:

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

Sie können es in .bashrc ablegen, wenn Sie möchten.

Ruanhao
quelle
0

Wenn beide Zeichenfolgen nacheinander angeordnet sind, setzen Sie auf grepBefehl ein Muster dazwischen :

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

Beispiel, wenn die folgenden Zeilen in einer Datei mit dem Namen enthalten sind Dockerfile:

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

So rufen Sie die Zeile ab, die die Zeichenfolgen enthält: FROM pythonund verwenden Sie as build-pythondann:

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

Dann zeigt die Ausgabe nur die Zeile, die beide Zeichenfolgen enthält :

FROM python:3.8 as build-python
Chetabahana
quelle
-2

ripgrep

Hier ist das Beispiel mit rg:

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

Es ist eines der schnellsten Grepping-Tools, da es auf der Regex-Engine von Rust basiert, die endliche Automaten, SIMD und aggressive Literal-Optimierungen verwendet, um die Suche sehr schnell zu machen.

Verwenden Sie es, insbesondere wenn Sie mit großen Datenmengen arbeiten.

Siehe auch zugehörige Funktionsanforderung bei GH-875 .

Kenorb
quelle
1
Diese Antwort ist nicht ganz richtig. Die genannten Erfassungsgruppen sind nicht erforderlich, und dies behandelt den Fall nicht, wenn er string2zuvor angezeigt wird string1. Die einfachste Lösung für dieses Problem ist rg string1 file.txt | rg string2.
BurntSushi5