grep สามารถส่งออกเฉพาะกลุ่มที่ระบุที่ตรงกันหรือไม่


290

บอกว่าฉันมีไฟล์:

# file: 'test.txt'
foobar bash 1
bash
foobar happy
foobar

ฉันแค่อยากรู้ว่าคำใดปรากฏหลัง "foobar" ดังนั้นฉันสามารถใช้ regex นี้:

"foobar \(\w\+\)"

วงเล็บแสดงว่าฉันมีความสนใจเป็นพิเศษในคำทันทีหลังจาก foobar แต่เมื่อฉันทำgrep "foobar \(\w\+\)" test.txtฉันจะได้ทั้งบรรทัดที่ตรงกับ regex ทั้งหมดมากกว่าเพียงแค่ "คำหลังจาก foobar":

foobar bash 1
foobar happy

ฉันชอบที่ผลลัพธ์ของคำสั่งนั้นจะเป็นดังนี้:

bash
happy

มีวิธีบอก grep ให้แสดงเฉพาะรายการที่ตรงกับการจัดกลุ่ม (หรือการจัดกลุ่มเฉพาะ) ในนิพจน์ทั่วไปหรือไม่?


4
สำหรับผู้ที่ไม่ต้องการ grep:perl -lne 'print $1 if /foobar (\w+)/' < test.txt
vault

คำตอบ:


325

GNU grep มี-Pตัวเลือกสำหรับ regex แบบ Perl และ-oตัวเลือกในการพิมพ์เฉพาะสิ่งที่ตรงกับรูปแบบ เหล่านี้สามารถนำมารวมกันโดยใช้ยืนยันมองไปรอบ ๆ (อธิบายภายใต้รูปแบบการขยายใน manpage perlre ) เพื่อเอาส่วนหนึ่งของรูปแบบ grep -oจากสิ่งที่ตั้งใจจะจับคู่กับวัตถุประสงค์ของ

$ grep -oP 'foobar \K\w+' test.txt
bash
happy
$

นี่\Kคือรูปแบบสั้น (และแบบฟอร์มที่มีประสิทธิภาพมากขึ้น) (?<=pattern)ซึ่งคุณใช้เป็นคำยืนยันแบบมองไม่เห็นความกว้างศูนย์ก่อนข้อความที่คุณต้องการส่งออก (?=pattern)สามารถใช้เป็นข้อความยืนยันล่วงหน้าที่มีความกว้างเป็นศูนย์หลังจากข้อความที่คุณต้องการส่งออก

ตัวอย่างเช่นหากคุณต้องการจับคู่คำระหว่างfooและbarคุณสามารถใช้:

$ grep -oP 'foo \K\w+(?= bar)' test.txt

หรือ (สำหรับสมมาตร)

$ grep -oP '(?<=foo )\w+(?= bar)' test.txt

3
คุณจะทำอย่างไรถ้า regex ของคุณมีมากกว่าการจัดกลุ่ม? (ตามชื่อโดยนัย?)
barracel

4
@ barracel: ฉันไม่เชื่อว่าคุณทำได้ เวลาสำหรับsed(1)
camh

1
@camh ฉันมีการทดสอบเพียงแค่ว่าgrep -oP 'foobar \K\w+' test.txtเอาท์พุทอะไรกับของ test.txtOP เวอร์ชัน grep คือ 2.5.1 มีอะไรผิดปกติ? O_O
SOUser

@ XichenLi: ฉันไม่สามารถพูดได้ ฉันเพิ่งสร้าง v2.5.1 จาก grep (มันค่อนข้างเก่า - จากปี 2549) และมันใช้งานได้สำหรับฉัน
camh

@ SOUser: ฉันมีประสบการณ์เดียวกัน - ไม่มีอะไรที่จะส่งออกไฟล์ ฉันส่งคำขอแก้ไขเพื่อรวม '>' ไว้ข้างหน้าชื่อไฟล์เพื่อส่งผลลัพธ์เนื่องจากทำงานได้ดีสำหรับฉัน
rjchicago

39

grep มาตรฐานไม่สามารถทำเช่นนี้ แต่รุ่นล่าสุดของ GNU grep สามารถ คุณสามารถหันไปเสก, awk หรือ perl นี่คือตัวอย่างเล็ก ๆ น้อย ๆ ที่ทำสิ่งที่คุณต้องการในอินพุตตัวอย่างของคุณ พวกเขาทำงานแตกต่างกันเล็กน้อยในกรณีมุม

แทนที่foobar word other stuffด้วยwordพิมพ์เฉพาะเมื่อมีการเปลี่ยนเท่านั้น

sed -n -e 's/^foobar \([[:alnum:]]\+\).*/\1/p'

หากคำแรกคือfoobarพิมพ์คำที่สอง

awk '$1 == "foobar" {print $2}'

ตัดfoobarถ้ามันเป็นคำแรกและข้ามบรรทัดมิฉะนั้น; จากนั้นตัดทุกอย่างหลังจากช่องว่างแรกและพิมพ์

perl -lne 's/^foobar\s+// or next; s/\s.*//; print'

น่ากลัว! ฉันคิดว่าฉันสามารถทำสิ่งนี้ได้ด้วยความใจเย็น แต่ฉันไม่เคยใช้มาก่อนและหวังว่าฉันจะใช้ความคุ้นเคยgrepได้ แต่ไวยากรณ์สำหรับคำสั่งเหล่านี้ดูเหมือนจริง ๆ แล้วตอนนี้ฉันคุ้นเคยกับการค้นหา vim-style & replace + regexes ขอบคุณมาก
Cory Klein

1
ไม่เป็นความจริง Gilles ดูคำตอบของฉันสำหรับโซลูชัน grep GNU
camh

1
@camh: อาฉันไม่ทราบว่า GNU grep ตอนนี้มีการสนับสนุน PCRE อย่างสมบูรณ์ ฉันแก้ไขคำตอบแล้วขอบคุณ
Gilles

1
คำตอบนี้มีประโยชน์อย่างยิ่งสำหรับ Linux แบบฝังเนื่องจาก Busybox grepไม่รองรับ PCRE
Craig McQueen

เห็นได้ชัดว่ามีหลายวิธีในการทำภารกิจเดียวกันที่นำเสนออย่างไรก็ตามถ้า OP ขอการใช้ grep ทำไมคุณตอบอย่างอื่น ย่อหน้าแรกของคุณไม่ถูกต้อง: ใช่ grep สามารถทำได้
fcm

32
    sed -n "s/^.*foobar\s*\(\S*\).*$/\1/p"

-n     suppress printing
s      substitute
^.*    anything before foobar
foobar initial search match
\s*    any white space character (space)
\(     start capture group
\S*    capture any non-white space character (word)
\)     end capture group
.*$    anything after the capture group
\1     substitute everything with the 1st capture group
p      print it

1
+1 สำหรับตัวอย่างที่ดูเหมือนว่าเป็นเครื่องมือที่ดีกว่าสำหรับ grep One comment, the ^and $extraneous เนื่องจาก.*เป็นการจับคู่โลภ อย่างไรก็ตามการรวมพวกเขาอาจช่วยให้ความกระจ่างเจตนาของ regex
Tony

18

ถ้าคุณรู้ว่า foobar นั้นเป็นคำแรกหรือบรรทัดเสมอคุณก็สามารถใช้คำสั่ง cut ได้ ชอบมาก

grep "foobar" test.file | cut -d" " -f2

-oสวิตช์ grep จะดำเนินการกันอย่างแพร่หลาย (moreso กว่าส่วนขยาย grep Gnu) ดังนั้นการทำgrep -o "foobar" test.file | cut -d" " -f2จะช่วยเพิ่มประสิทธิภาพของการแก้ปัญหานี้ซึ่งเป็นแบบพกพามากขึ้นกว่าการใช้ยืนยัน lookbehind
dubiousjim

ฉันเชื่อว่าคุณจะต้องการgrep -o "foobar .*"หรือgrep -o "foobar \w+".
G-Man

9

หากไม่รองรับ PCRE คุณสามารถบรรลุผลลัพธ์เดียวกันโดยใช้การเรียกใช้ grep สองครั้ง ตัวอย่างเช่นการคว้าคำหลังจากfoobarทำสิ่งนี้:

<test.txt grep -o 'foobar  *[^ ]*' | grep -o '[^ ]*$'

สิ่งนี้สามารถขยายเป็นคำที่กำหนดเองหลังจากfoobarเช่นนี้ (พร้อม EREs สำหรับการอ่าน):

i=1
<test.txt egrep -o 'foobar +([^ ]+ +){'$i'}[^ ]+' | grep -o '[^ ]*$'

เอาท์พุท:

1

หมายเหตุดัชนีiเป็นแบบศูนย์


6

pcregrepมี-oตัวเลือกที่ชาญฉลาดที่ช่วยให้คุณเลือกกลุ่มการจับภาพที่คุณต้องการออก ดังนั้นใช้ไฟล์ตัวอย่างของคุณ

$ pcregrep -o1 "foobar (\w+)" test.txt
bash
happy

4

ใช้grepไม่ได้ข้ามแพลตฟอร์มที่รองรับตั้งแต่-P/ --perl-regexpจะใช้ได้เฉพาะในGNUgrepไม่BSDgrep

นี่คือวิธีใช้ripgrep:

$ rg -o "foobar (\w+)" -r '$1' <test.txt
bash
happy

ตามman rg:

-r/ --replace REPLACEMENT_TEXTแทนที่ทุกการแข่งขันด้วยข้อความที่กำหนด

ดัชนีกลุ่มการจับภาพ (เช่น, $5) และชื่อ (เช่น$foo) ได้รับการสนับสนุนในสตริงการแทนที่

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


2

ฉันพบคำตอบของ @jgshawkey มีประโยชน์มาก grepไม่ใช่เครื่องมือที่ดีสำหรับเรื่องนี้ แต่ sed คือแม้ว่าที่นี่เรามีตัวอย่างที่ใช้ grep เพื่อคว้าสายที่เกี่ยวข้อง

ไวยากรณ์ของ Regex ของ sed นั้นมีลักษณะเฉพาะถ้าคุณไม่คุ้นเคย

นี่คือตัวอย่างอื่น: อันนี้แยกวิเคราะห์ผลลัพธ์ของ xinput เพื่อรับจำนวนเต็ม ID

⎜   ↳ SynPS/2 Synaptics TouchPad                id=19   [slave  pointer  (2)]

และฉันต้องการ 19

export TouchPadID=$(xinput | grep 'TouchPad' | sed  -n "s/^.*id=\([[:digit:]]\+\).*$/\1/p")

หมายเหตุไวยากรณ์คลาส:

[[:digit:]]

และจำเป็นต้องหลบหนีจากสิ่งต่อไปนี้ +

ฉันถือว่าการจับคู่บรรทัดเดียวเท่านั้น


นี่คือสิ่งที่ฉันพยายามทำ ขอบคุณ!
James

รุ่นที่เรียบง่ายขึ้นเล็กน้อยโดยไม่มีการเสริมgrepสมมติว่า 'ทัชแพด' อยู่ทางซ้ายของ 'id':echo "SynPS/2 Synaptics TouchPad id=19 [slave pointer (2)]" | sed -nE "s/.*TouchPad.+id=([0-9]+).*/\1/p"
Amit Naidu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.