จับคู่สองสตริงในหนึ่งบรรทัดด้วย grep


218

ฉันพยายามใช้grepเพื่อจับคู่บรรทัดที่มีสองสตริงที่แตกต่างกัน ฉันลองต่อไปนี้ แต่ตรงกับบรรทัดที่มีทั้งstring1 หรือ string2ซึ่งไม่ใช่สิ่งที่ฉันต้องการ

grep 'string1\|string2' filename

ดังนั้นฉันจะจับคู่กับgrepบรรทัดที่มีทั้งสองสตริงได้อย่างไร


1
ที่เกี่ยวข้อง: unix.stackexchange.com/questions/37313/ …
AlikElzin-kilaka

คำตอบ:


189

คุณสามารถใช้ได้ grep 'string1' filename | grep 'string2'

หรือ, grep 'string1.*string2\|string2.*string1' filename


5
@AlexanderN แน่นอนฉันลาดเททำให้มันทำงานร่วมกับหลาย, thats เพื่อให้แปลกได้รับการยอมรับ ..
ราศีกุมภ์พลังงาน

1
มันไม่ใช่คำถามหลายบรรทัด ถ้ามันเป็นหลาย, -P grep สนับสนุน regex สไตล์ Perl ...
สกอตต์ Prive

20
ใช้ได้เฉพาะเมื่อทั้ง 'string1' และ 'string2' อยู่ในบรรทัดเดียวกัน หากคุณต้องการค้นหาบรรทัดที่มี 'string1' หรือ 'string2' ดูคำตอบของผู้ใช้ 45949
lifeson106

10
ตัวเลือกแรก: piping หนึ่ง grep เป็นวินาทีไม่ก่อให้เกิดผลหรือมันผลิตและผล
masukomi

1
ฉันใช้แล้วgrep -e "string1" -e "string2"
Ravi Dhoriya ツ

198

ฉันคิดว่านี่คือสิ่งที่คุณกำลังมองหา:

grep -E "string1|string2" filename

ฉันคิดว่าคำตอบเช่นนี้:

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

ตรงกับกรณีที่ทั้งสองมีอยู่ไม่หนึ่งหรืออื่น ๆ หรือทั้งสองอย่าง


14
จะไม่grep -e "string1" -e "string2" filenameทำเช่นเดียวกัน?
janosdivenyi

25
นี่คือวิธีการ grep สำหรับ string1 หรือ string2 คำถามระบุอย่างชัดเจนว่าพวกเขากำลังมองหา string1 AND string2
orion elenzil

9
ค่อนข้างแน่ใจว่าคำถามนั้นค่อนข้างแม่นยำ:How do I match lines that contains *both* strings?
r0estir0bbe

สามารถพิมพ์ด้วยบรรทัดเดียวกันได้หรือไม่?
吴毅凡

1
ทำไมคำตอบนี้ยังอยู่ที่นี่ มันไม่ใช่คำตอบสำหรับคำถาม
โพร

26

หากต้องการค้นหาไฟล์ที่มีคำทั้งหมดในลำดับใดก็ได้:

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

grep แรกเริ่มต้นจากการค้นหาแบบเรียกซ้ำ ( r), ไม่สนใจขนาดตัวพิมพ์ ( i) และรายชื่อ (พิมพ์ออกมา) ชื่อของไฟล์ที่กำลังจับคู่ ( l) สำหรับหนึ่งคำ ('action'ที่มีราคาเดียว) ที่เกิดขึ้นได้ทุกที่ในไฟล์

greps ที่ตามมาค้นหาคำอื่น ๆ รักษาความไม่รู้สึกตัวเล็กและรายการออกไฟล์ที่ตรงกัน

รายการสุดท้ายของไฟล์ที่คุณจะได้รับคือไฟล์ที่มีข้อกำหนดเหล่านี้ในลำดับใดก็ได้ในไฟล์


2
ตกลงกัน! ฉันจะทราบว่าฉันต้องให้ xargs "-d '\ n'" เพื่อจัดการชื่อไฟล์ด้วยช่องว่าง สิ่งนี้ใช้ได้กับฉันบน Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Tommy Harris

16

หากคุณมีgrepกับ-Pตัวเลือกสำหรับการ จำกัดperlregex คุณสามารถใช้

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

ซึ่งมีข้อได้เปรียบในการทำงานกับสตริงที่ทับซ้อนกัน มันค่อนข้างตรงไปตรงมาใช้perlเป็นgrepเพราะคุณสามารถระบุและตรรกะได้โดยตรง:

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

1
คำตอบที่ดีที่สุด เชลล์นั้นง่ายและรวดเร็ว แต่เมื่อรูปแบบซับซ้อนคุณควรใช้ Python หรือ Perl (หรือ Awk) อย่าตีหัวของคุณกับกำแพงพยายามที่จะพิสูจน์ว่ามันสามารถทำได้ในเปลือกบริสุทธิ์ (สิ่งที่หมายถึงวันนี้) เครื่องมือเตือนความจำเครื่องมือเหล่านี้สามารถใช้ในไวยากรณ์ "หนึ่งซับ" ที่ฝัง dibble ลงในเชลล์สคริปต์ที่มีอยู่
Scott Prive

12

วิธีการของคุณเกือบจะดีหาย -w

grep -w 'string1\|string2' filename

1
อย่างน้อยใน OS-X และ FreeBSD มันใช้งานได้! ฉันเดาว่าคุณอยู่ในอย่างอื่น (ซึ่ง OP ไม่ได้กำหนดไว้หวังว่าคุณจะไม่ตอบคำถามที่ถูกต้องสำหรับผู้ใช้หลายคนยกเว้นคุณ)
Leo

ฉันอยู่บน OS-X บางทีฉันไม่ได้ทำอย่างถูกต้อง? ดูสิ่งที่ฉันทำ: i.imgur.com/PFVlVAG.png
Ariel

1
แปลก ฉันคาดหวังว่าความแตกต่างนั้นไม่ได้เป็นไฟล์ แต่ถ้าฉันใช้วิธีการของฉันกับ ls ฉันจะได้ผลลัพธ์ที่คุณไม่ได้ทำ: imgur.com/8eTt3Ak.png - ทั้งบน OS-X 10.9.5 ( "grep (BSD grep) 2.5.1-FreeBSD") และ FreeBSD 10 ("grep (GNU grep) 2.5.1-FreeBSD") ฉันอยากรู้ว่าคุณgrep -Vเป็นอะไร
Leo

1
ตัวอย่างของคุณใช้ได้สำหรับฉัน: i.imgur.com/K8LM69O.pngดังนั้นความแตกต่างก็คือเมธอดนี้ไม่รับสายย่อยพวกเขาจะต้องเป็นสายอักขระที่สมบูรณ์ด้วยตัวเอง ฉันเดาว่าคุณจะต้องสร้าง regexps ภายใน grep เพื่อค้นหาสตริงย่อย บางสิ่งเช่นนี้:grep -w 'regexp1\|regexp2' filename
Ariel

2
OP แสดงตัวอย่างโดยการจับคู่ string1 หรือ string2 และถามถึงวิธีจับคู่บรรทัดที่มีทั้งสองสตริง ตัวอย่างนี้ยังคงให้ผลตอบแทนหรือ
gustafbstrom

7

|ผู้ประกอบการในการแสดงออกปกติหรือหมายความว่า กล่าวคือ string1 หรือ string2 จะตรงกัน คุณสามารถทำได้:

grep 'string1' filename | grep 'string2'

ซึ่งจะไพพ์ผลลัพธ์จากคำสั่งแรกไปยัง grep ตัวที่สอง ที่ควรให้เฉพาะบรรทัดที่ตรงกับทั้งคู่


1
ข้อความของคุณเป็นจริง แต่อย่าตอบคำถาม OP
เบ็นวีลเลอร์

นี่จะตอบคำถามและนี่คือวิธีที่คนส่วนใหญ่เขียน
ปีเตอร์ K


4

และเป็นคนที่แนะนำ Perl และงูหลามและซับซ้อนเชลล์สคริปต์ที่นี่ง่ายawkวิธีการ:

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

ต้องดูความคิดเห็นต่อคำตอบที่ยอมรับได้: ไม่นี่ไม่ได้ทำหลายบรรทัด แต่นั่นไม่ใช่สิ่งที่ผู้เขียนคำถามถาม


3

อย่าพยายามใช้ grep สำหรับเรื่องนี้ใช้ awk แทน ในการจับคู่ 2 regexps R1 และ R2 ใน grep คุณคิดว่ามันจะเป็น:

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

ในขณะที่ awk มันจะเป็น:

awk '/R1/ && /R2/'

แต่ถ้าR2การทับซ้อนที่มีหรือเป็นส่วนหนึ่งของR1? คำสั่ง grep นั้นจะไม่ทำงานในขณะที่คำสั่ง awk จะทำงาน ให้บอกว่าคุณต้องการค้นหาบรรทัดที่มีtheและheat:

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

คุณต้องใช้ 2 greps และไปป์:

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

และแน่นอนว่าถ้าคุณต้องการให้แยกมันออกมาคุณสามารถเขียน regexp แบบเดียวกับที่คุณใช้ใน grep และมีตัวเลือก awk ทางเลือกที่ไม่เกี่ยวข้องกับการทำซ้ำ regexps ในทุกลำดับที่เป็นไปได้

หากคุณต้องการขยายโซลูชันของคุณให้ตรงกับ 3 regexps R1, R2 และ R3 ใน grep ที่เป็นหนึ่งในตัวเลือกที่ไม่ดีเหล่านี้:

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

ในขณะที่อยู่ใน awk มันจะกระชับชัดเจนง่ายมีประสิทธิภาพ:

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

ทีนี้ถ้าคุณต้องการจับคู่สตริง S1 และ S2 แทน regexps R1 และ R2 คุณไม่สามารถทำเช่นนั้นได้ในการเรียก grep เพียงครั้งเดียวคุณต้องเขียนโค้ดเพื่อหลบหลีกเมตาคาร์ RE ทั้งหมดก่อนที่จะเรียก grep:

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

หรือใช้ 2 greps และไปป์อีกครั้ง:

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

ซึ่งเป็นตัวเลือกที่ไม่ดีอีกครั้งในขณะที่ awk คุณเพียงแค่ใช้ตัวดำเนินการสตริงแทนตัวดำเนินการ regexp:

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

ทีนี้ถ้าคุณต้องการจับคู่ 2 regexps ในย่อหน้ามากกว่าหนึ่งบรรทัด ไม่สามารถทำได้ใน grep, เล็กน้อยใน awk:

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

แล้วไฟล์ทั้งหมดล่ะ? ไม่สามารถทำได้ใน grep และ trivial ใน awk อีกครั้ง (ตอนนี้ฉันใช้ GNU awk สำหรับ multi-char RS สำหรับความรัดกุม แต่มันไม่ได้รหัสมากขึ้นใน awk ใด ๆ หรือคุณสามารถเลือกตัวควบคุม-char ที่คุณรู้ว่าจะไม่ อยู่ในอินพุตสำหรับ RS เพื่อทำสิ่งเดียวกัน):

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

ดังนั้น - หากคุณต้องการค้นหาหลาย ๆ regexps หรือสายอักขระในบรรทัดหรือย่อหน้าหรือไฟล์แล้วไม่ใช้ grep ใช้ awk


ตรงawk '/R1/ && /R2/'ตามตัวพิมพ์ใหญ่ - เล็ก
โพร

@Hashim - ไม่ เพื่อให้ตรงตามตัวพิมพ์ใหญ่ - เล็กด้วย GNU awk คุณต้องทำawk -v IGNORECASE=1 '/R1/ && /R2/'และกับ awk ใด ๆawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ed Morton


2

พบบรรทัดที่เริ่มต้นด้วย 6 ช่องว่างและจบด้วย:

 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

สมมติว่าเราต้องการค้นหาคำหลายคำในไฟล์ testfile มีสองวิธีที่จะไปเกี่ยวกับเรื่องนี้

1) ใช้คำสั่ง grep กับรูปแบบการจับคู่ regex

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

2) ใช้คำสั่ง egrep

egrep -c 'DOG|CAT' testfile 

ด้วย egrep คุณไม่จำเป็นต้องกังวลเกี่ยวกับการแสดงออกและเพียงแค่แยกคำด้วยตัวคั่นท่อ


2

git grep

นี่คือไวยากรณ์ที่ใช้git grepกับหลายรูปแบบ:

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

นอกจากนี้คุณยังอาจรวมรูปแบบกับบูลีนการแสดงออกเช่น--and, และ--or--not

ตรวจสอบman git-grepความช่วยเหลือ


--all-matchเมื่อให้การแสดงออกรูปแบบหลายสถานะนี้มีการระบุการจำกัด การแข่งขันไปยังไฟล์ที่มีสายเพื่อให้ตรงกับทั้งหมดของพวกเขา

--no-index ค้นหาไฟล์ในไดเรกทอรีปัจจุบันที่ไม่ได้รับการจัดการโดย Git

-l/ --files-with-matches/ --name-onlyแสดงเฉพาะชื่อไฟล์

-eพารามิเตอร์ถัดไปคือรูปแบบ เริ่มต้นคือการใช้ regexp ขั้นพื้นฐาน

พารามิเตอร์อื่น ๆ ที่ควรพิจารณา:

--threads จำนวนเธรดผู้ทำงาน grep ที่จะใช้

-q/ --quiet/ --silentอย่าส่งออกเส้นที่ตรงกัน; ออกด้วยสถานะ 0 เมื่อมีการแข่งขัน

หากต้องการเปลี่ยนประเภทรูปแบบคุณอาจใช้-G/ --basic-regexp(ค่าเริ่มต้น), -F/ --fixed-strings, -E/ --extended-regexp, -P/ --perl-regexp,-f fileและอื่น ๆ

ที่เกี่ยวข้อง:

สำหรับหรือการดำเนินการโปรดดูที่:


2
คิดอยู่เสมอว่า "git grep" สามารถทำงานได้ภายในที่เก็บ git เท่านั้น ฉันไม่ทราบตัวเลือก --no-index ขอบคุณที่ชี้นำ!
Kamaraju Kusumanchi

1

วางสตริงที่คุณต้องการ grep ไว้ในไฟล์

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

จากนั้นค้นหาโดยใช้ -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 

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

จะได้รับสายกับ string1 และ string2 ในลำดับใด ๆ


ในทางใดที่แตกต่างจากคำตอบสองข้ออย่างน้อยที่สุด?
luk2302

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

สิ่งนี้ใช้ได้กับการจับคู่คำที่ตรงกันและคำที่ไม่ตรงตามตัวพิมพ์ใหญ่ - เล็กสำหรับการใช้ -i


0

สำหรับการแข่งขันหลายสาย:

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

หรือ

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

เราเพียงแค่ต้องการลบอักขระบรรทัดใหม่และใช้งานได้!


0

คุณควรมีgrepสิ่งนี้:

$ grep 'string1' file | grep 'string2'

1
สิ่งนี้ทำตรรกะ AND OP ต้องการตรรกะหรือ
Ben Wheeler

1
@BenWheeler: จากคำถาม: "แล้วฉันจะจับคู่กับ grep เฉพาะบรรทัดที่มีทั้งสองสายได้อย่างไร?"
เอริคฉัน

0

ฉันมักพบปัญหาเดียวกับของคุณและฉันเพิ่งเขียนสคริปต์บางส่วน:

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
}

การใช้งาน:

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

คุณสามารถใส่ไว้ใน. bashrc หากคุณต้องการ


0

เมื่อทั้งสองสายอยู่ในลำดับแล้ววางรูปแบบในระหว่างgrepคำสั่งเมื่อ:

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

ตัวอย่างถ้าบรรทัดต่อไปนี้มีอยู่ในไฟล์ชื่อDockerfile:

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

ที่จะได้รับบรรทัดที่ประกอบด้วยสตริง: FROM pythonและas build-pythonการใช้งานแล้ว:

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

จากนั้นเอาต์พุตจะแสดงเฉพาะบรรทัดที่มีทั้งสองสตริง :

FROM python:3.8 as build-python

-2

ripgrep

นี่คือตัวอย่างการใช้rg:

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

เป็นหนึ่งในเครื่องมือ grepping ที่เร็วที่สุดเนื่องจากมันถูกสร้างขึ้นจากเครื่องมือ regex ของ Rustซึ่งใช้ออโต้ไฟน์ จำกัด , SIMD และการเพิ่มประสิทธิภาพตัวอักษรที่ก้าวร้าวเพื่อให้การค้นหารวดเร็วมาก

ใช้มันโดยเฉพาะเมื่อคุณทำงานกับข้อมูลขนาดใหญ่

ดูคำขอคุณสมบัติยังในGH-875


1
คำตอบนี้ไม่ถูกต้องนัก กลุ่มการจับภาพที่ระบุชื่อนั้นไม่จำเป็นและสิ่งนี้จะไม่จัดการกรณีและปัญหาเมื่อstring2ปรากฏก่อนหน้าstring1นี้ rg string1 file.txt | rg string2ทางออกที่ง่ายที่สุดในการแก้ไขปัญหานี้คือ
BurntSushi5
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.