Połącz dwa ciągi w jednym wierszu z grep

218

Próbuję użyć, grepaby dopasować wiersze zawierające dwa różne ciągi. Próbowałem następujących, ale to pasuje do wierszy zawierających ciąg1 lub ciąg2, które nie są tym, czego chcę.

grep 'string1\|string2' filename

Jak więc dopasować greptylko linie zawierające oba ciągi ?

słyszysz
źródło
1
Powiązane: unix.stackexchange.com/questions/37313/…
AlikElzin-kilaka

Odpowiedzi:

189

Możesz użyć grep 'string1' filename | grep 'string2'

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

dheerozaur
źródło
5
@AlexanderN rzeczywiście nie mogę sprawić, by działało z multilinią, to takie dziwne, że zostało zaakceptowane ..
Aquarius Power
1
To nie było pytanie wieloliniowe. Gdyby był wielowierszowy, grep -P wspiera regex stylu Perla ...
Scott Prive
20
Działa tylko, gdy oba „ciąg1” ORAZ „ciąg2” znajdują się w tym samym wierszu. Jeśli chcesz znaleźć wiersze z ciągiem „ciąg1” lub „ciąg2”, zobacz odpowiedź użytkownika 45949.
lifeson106
10
pierwsza opcja: pipowanie jednego grepa w sekundę NIE daje wyniku LUB powoduje wynik AND.
masukomi
1
Użyłemgrep -e "string1" -e "string2"
Ravi Dhoriya ツ
198

Myślę, że tego właśnie szukałeś:

grep -E "string1|string2" filename

Myślę, że takie odpowiedzi:

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

pasują tylko do przypadku, gdy oba są obecne, a nie jedno lub drugie lub oba.

użytkownik45949
źródło
14
nie grep -e "string1" -e "string2" filenamezrobiłby tego samego?
janosdivenyi
25
oto jak grep dla string1 LUB string2. pytanie wyraźnie stwierdza, że ​​szukają string1 AND string2.
orion elenzil
9
Całkiem pewne, że pytanie jest dość precyzyjne:How do I match lines that contains *both* strings?
r0estir0bbe
Czy można drukować z tą samą linią?
吴毅 凡
1
Dlaczego ta odpowiedź wciąż tu jest? To NIE jest odpowiedź na pytanie.
Prometeusz
26

Aby wyszukać pliki zawierające wszystkie słowa w dowolnej kolejności w dowolnym miejscu:

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

Pierwszy grep uruchamia wyszukiwanie rekurencyjne ( r), ignorując iwielkość liter ( ) i wyświetlając (drukując) nazwy plików pasujących ( l) dla jednego terminu ( 'action'z pojedynczymi cudzysłowami) występujących w dowolnym miejscu pliku.

Kolejne greps szukają innych terminów, zachowując rozróżnianie wielkości liter i wypisując pasujące pliki.

Ostateczna lista plików, które otrzymasz, będzie zawierać te warunki, w dowolnej kolejności w dowolnym miejscu pliku.

Kinjal Dixit
źródło
2
Zgoda! Po prostu zauważę, że musiałem nadać xargsowi „-d '\ n'”, aby obsługiwać nazwy plików ze spacjami. To działało dla mnie w systemie Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Tommy Harris
16

Jeśli masz grepz -Popcją ograniczonego perlregex, można użyć

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

który ma tę zaletę, że pracuje z nakładającymi się łańcuchami. Jest to nieco prostsze przy użyciu perlas grep, ponieważ możesz określić i logikę bardziej bezpośrednio:

perl -ne 'print if /string1/ && /string2/'
tchrist
źródło
1
Najlepsza odpowiedź. Powłoka jest bardzo łatwa i szybka, ale gdy wzór się skomplikuje, powinieneś użyć Pythona lub Perla (lub Awka). Nie uderzaj głową o ścianę, próbując udowodnić, że można to zrobić w czystej skorupie (cokolwiek to znaczy w dzisiejszych czasach). Przypominamy, narzędzia te mogą być używane w składni „one liner”, które są osadzone dibble w istniejącym skrypcie powłoki.
Scott Prive
12

Twoja metoda była prawie dobra, brakuje tylko -w

grep -w 'string1\|string2' filename
Lew
źródło
1
Przynajmniej na OS-X i FreeBSD to działa! Domyślam się, że zajmujesz się czymś innym (czego OP nie zdefiniował - mam nadzieję, że nie głosowałeś poprawnej odpowiedzi wielu użytkownikom oprócz ciebie).
Leo
Jestem na OS-X. Być może nie robię tego poprawnie? Zobacz, co zrobiłem: i.imgur.com/PFVlVAG.png
Ariel
1
Dziwny. Spodziewałem się, że różnica polega na tym, że nie wkradam się do pliku, ale jeśli potokuję moją metodę za pomocą twojego ls, otrzymuję wynik, że nie: imgur.com/8eTt3Ak.png - Oba na OS-X 10.9.5 ( „grep (BSD grep) 2.5.1-FreeBSD”) i FreeBSD 10 („grep (GNU grep) 2.5.1-FreeBSD”). Jestem ciekawa, jaka jest twoja grep -V.
Leo
1
Twoje przykłady działają dla mnie: i.imgur.com/K8LM69O.png Tak więc różnica polega na tym, że ta metoda nie wykrywa podciągów, muszą same być kompletnymi ciągami. Myślę, że będziesz musiał konstruować wyrażenia regularne w grep, aby szukać podciągów. Coś w tym stylu:grep -w 'regexp1\|regexp2' filename
Ariel
2
OP pokazuje przykład, dopasowując łańcuch1 lub łańcuch2 i pyta, jak dopasować linie zawierające oba łańcuchy. Ten przykład nadal daje OR.
gustafbstrom
7

|Operator w wyrażeniu regularnym oznacza albo. To znaczy, że albo string1 albo string2 będą pasować. Mógłbyś:

grep 'string1' filename | grep 'string2'

który potokuje wyniki pierwszego polecenia do drugiego grep. To powinno dać ci tylko linie, które pasują do obu.

martineno
źródło
1
Twoje stwierdzenia są prawdziwe, ale nie odpowiadaj na pytanie OP
Ben Wheeler
To odpowiada na pytanie i tak właśnie pisze większość ludzi.
Peter K,
7

Możesz spróbować czegoś takiego:

(pattern1.*pattern2|pattern2.*pattern1)
Dorn
źródło
4

I jak ludzie sugerowali perl i python oraz zawiłe skrypty powłoki, tutaj proste podejście awk :

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

Po przejrzeniu komentarzy do zaakceptowanej odpowiedzi: nie, to nie robi wielu linii; ale nie o to też pytał autor pytania.

zadzwonić
źródło
3

Nie próbuj do tego używać grep, zamiast tego użyj awk. Aby dopasować 2 wyrażenia regularne R1 i R2 w grep, można by pomyśleć, że będzie to:

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

w awk będzie to:

awk '/R1/ && /R2/'

ale co jeśli R2nakłada się na podzbiór lub jest jego podzbiorem R1? To polecenie grep po prostu nie zadziałałoby, podczas gdy polecenie awk. Powiedzmy, że chcesz znaleźć wiersze zawierające thei heat:

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

Musisz do tego użyć 2 grepsa i fajki:

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

i oczywiście, jeśli rzeczywiście wymagałeś ich oddzielności, zawsze możesz napisać w awk ten sam wyrażenie regularne, którego używałeś w grep, i istnieją alternatywne rozwiązania awk, które nie wymagają powtarzania wyrażeń regularnych w każdej możliwej sekwencji.

Odkładając to na bok, co jeśli chcesz rozszerzyć swoje rozwiązanie, aby pasowało do 3 wyrażeń regularnych R1, R2 i R3. W grep byłby to jeden z tych złych wyborów:

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

podczas gdy w awk byłoby zwięzłe, oczywiste, proste, wydajne:

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

A jeśli teraz chcesz dopasować dosłowne ciągi znaków S1 i S2 zamiast wyrażeń regularnych R1 i R2? Po prostu nie możesz tego zrobić w jednym wywołaniu grep, musisz albo napisać kod, aby uciec od wszystkich metazarów RE przed wywołaniem grep:

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

lub ponownie użyj 2 greps i fajki:

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

które znowu są złymi wyborami, podczas gdy w awk po prostu używasz operatora łańcuchowego zamiast operatora wyrażenia regularnego:

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

Co teraz, jeśli chcesz dopasować 2 wyrażenia regularne w akapicie zamiast w wierszu? Nie można tego zrobić w grep, trywialny w awk:

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

Co powiesz na cały plik? Znowu nie można tego zrobić w grep i trywialnym w awk (tym razem używam GNU awk dla wieloznakowego RS dla zwięzłości, ale nie jest to dużo więcej kodu w żadnym awk lub możesz wybrać znak kontrolny, o którym wiesz, że nie będzie być na wejściu, aby RS zrobił to samo):

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

Więc - jeśli chcesz znaleźć wiele wyrażeń regularnych lub ciągów w linii, akapicie lub pliku, nie używaj grep, użyj awk.

Ed Morton
źródło
Czy awk '/R1/ && /R2/'wielkość liter nie rozróżnia?
Prometeusz
@Hashim - nie. Aby uniknąć rozróżniania wielkości liter w GNU awk, który zrobiłbyś awk -v IGNORECASE=1 '/R1/ && /R2/'i przy każdym awkawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ed Morton
3
grep string1\|string2 FILENAME 

GNU grep wersja 3.1

tilikoom
źródło
2

Znaleziono linie, które zaczynają się tylko 6 spacjami i kończą:

 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
źródło
2

Powiedzmy, że musimy znaleźć liczbę wielu słów w pliku testowym pliku. Można to zrobić na dwa sposoby

1) Użyj polecenia grep z wzorcem dopasowania wyrażenia regularnego

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

2) Użyj polecenia egrep

egrep -c 'DOG|CAT' testfile 

Dzięki egrep nie musisz się martwić o wyrażenie i po prostu oddzielić słowa separatorem potokowym.

Amit Singh
źródło
2

git grep

Oto składnia przy użyciu git grepwielu wzorców:

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

Można również łączyć wzory z logicznych wyrażeń takich jak --and, --ori --not.

Sprawdź man git-greppomoc.


--all-matchPrzy podawaniu wielu wyrażeń wzorcowych, ta flaga jest określona, ​​aby ograniczyć dopasowanie do plików, które mają linie pasujące do wszystkich .

--no-index Wyszukaj pliki w bieżącym katalogu, który nie jest zarządzany przez Git.

-l/ --files-with-matches/ --name-onlyPokaż tylko nazwy plików.

-eKolejnym parametrem jest wzorzec. Domyślnie jest używane podstawowe wyrażenie regularne.

Inne parametry do rozważenia:

--threads Liczba wątków roboczych grep do użycia.

-q/ --quiet/ --silentNie wysyłaj dopasowanych linii; wyjdź ze statusem 0, gdy pojawi się dopasowanie.

Aby zmienić typ wzoru, można również użyć -G/ --basic-regexp(domyślnie), -F/ --fixed-strings, -E/ --extended-regexp, -P/ --perl-regexp, -f filei inne.

Związane z:

Dla operacji LUB zobacz:

kenorb
źródło
2
Zawsze uważałem, że „git grep” można uruchomić tylko w repozytorium git. Nie wiedziałem o opcji --no-index. Dzięki za wskazanie tego!
Kamaraju Kusumanchi
1

Umieść ciągi, które chcesz grepować w pliku

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

Następnie wyszukaj za pomocą -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 
Tim Seed
źródło
1
grep '(string1.*string2 | string2.*string1)' filename

otrzyma wiersz ze string1 i string2 w dowolnej kolejności

James
źródło
W jaki sposób różni się to od co najmniej dwóch pierwszych odpowiedzi?
luk2302
1
grep -i -w 'string1\|string2' filename

Działa to w przypadku dokładnego dopasowywania słów i dopasowywania wyrazów bez rozróżniania wielkości liter, ponieważ używa się -i

Saurabh
źródło
0

dla dopasowania wieloliniowego:

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

lub

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

musimy tylko usunąć znak nowej linii i to działa!

Wodnik Moc
źródło
0

Powinieneś mieć greptakie:

$ grep 'string1' file | grep 'string2'
Raghuram
źródło
1
Wykonuje to logiczne AND. OP chce logicznego OR.
Ben Wheeler,
1
@BenWheeler: Z pytania: „Więc jak dopasować grep tylko do linii zawierających oba ciągi?”
Erik I
0

Często napotykam ten sam problem, co twój i właśnie napisałem fragment skryptu:

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
}

Stosowanie:

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

Możesz umieścić go w .bashrc, jeśli chcesz.

ruanhao
źródło
0

Kiedy oba ciągi są w sekwencji, wstaw wzór pomiędzy grepkomendą:

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

Przykład, jeśli w pliku o nazwie znajdują się następujące wiersze Dockerfile:

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

Aby uzyskać wiersz zawierający ciągi: FROM pythona as build-pythonnastępnie użyj:

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

Następnie wynik wyświetli tylko wiersz zawierający oba ciągi :

FROM python:3.8 as build-python
Chetabahana
źródło
-2

ripgrep

Oto przykład z użyciem rg:

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

Jest to jedno z najszybszych narzędzi grepowania, ponieważ zostało zbudowane na silniku regularnym Rdza który wykorzystuje skończone automaty, SIMD i agresywne optymalizacje dosłowne, aby wyszukiwanie było bardzo szybkie.

Użyj go, zwłaszcza gdy pracujesz z dużymi danymi.

Zobacz także powiązane żądanie funkcji w GH-875 .

kenorb
źródło
1
Ta odpowiedź nie jest do końca właściwa. Nazwane grupy przechwytywania są niepotrzebne, a to nie obsługuje przypadku, gdy string2pojawia się wcześniej string1. Najprostszym rozwiązaniem tego problemu jest rg string1 file.txt | rg string2.
BurntSushi5