ฉันจะ grep สำหรับบรรทัดที่มีคำใดคำหนึ่งในสองคำ แต่ไม่ใช่ทั้งสองคำได้อย่างไร


25

ฉันพยายามใช้grepเพื่อแสดงเฉพาะบรรทัดที่มีคำใดคำหนึ่งในสองคำหากมีเพียงคำเดียวที่ปรากฏในบรรทัด แต่ไม่ใช่หากอยู่ในบรรทัดเดียวกัน

จนถึงตอนนี้ฉันพยายามแล้ว grep pattern1 | grep pattern2 | ...แต่ไม่ได้ผลลัพธ์ที่ฉันคาดไว้


(1) คุณพูดถึง "คำศัพท์" และ "รูปแบบ" มันคืออะไร คำสามัญเช่น "ด่วน", "น้ำตาล" และ "จิ้งจอก" หรือการแสดงออกปกติเป็น[a-z][a-z0-9]\(,7\}\(\.[a-z0-9]\{,3\}\)+อย่างไร (2) จะเกิดอะไรขึ้นหากคำใดคำหนึ่ง / รูปแบบปรากฏมากกว่าหนึ่งครั้งในหนึ่งบรรทัด (และอีกอันหนึ่งไม่ปรากฏ) นั่นเท่ากับคำที่ปรากฏครั้งเดียวหรือนับเป็นหลายครั้งหรือไม่
G-Man พูดว่า 'Reinstate Monica'

คำตอบ:


59

เครื่องมืออื่นที่ไม่ใช่grepวิธีที่จะไป

ยกตัวอย่างเช่นการใช้ Perl คำสั่งจะเป็น:

perl -ne 'print if /pattern1/ xor /pattern2/'

perl -neรันคำสั่งที่กำหนดเหนือแต่ละบรรทัดของ stdin ซึ่งในกรณีนี้พิมพ์บรรทัดหากตรง/pattern1/ xor /pattern2/หรือในคำอื่น ๆ ที่ตรงกับหนึ่งรูปแบบ แต่ไม่อื่น ๆ (พิเศษหรือ)

สิ่งนี้ใช้ได้กับรูปแบบตามลำดับใด ๆ และควรมีประสิทธิภาพที่ดีกว่าการเรียกใช้หลายรายการgrepและพิมพ์น้อยลงเช่นกัน

หรือสั้นกว่าด้วย awk:

awk 'xor(/pattern1/,/pattern2/)'

หรือสำหรับรุ่น awk ที่ไม่มีxor:

awk '/pattern1/+/pattern2/==1`

4
ดี - Awk xorมีใน GNU Awk เท่านั้นหรือไม่
ขับรถเหล็ก

9
@steeldriver ฉันคิดว่ามันเป็น GNU เท่านั้นใช่ หรืออย่างน้อยก็หายไปกับรุ่นเก่ากว่า คุณสามารถแทนที่มันด้วย/pattern1/+/pattern2/==1ir xorจะหายไป
Chris

4
@JimL คุณสามารถใส่ขอบเขตของคำ ( \b) \bword\bในรูปแบบของตัวเองคือ
wjandrea

4
@vikingsteve หากคุณต้องการใช้ grep โดยเฉพาะมีคำตอบมากมายที่นี่ แต่สำหรับคนที่ต้องการทำงานให้สำเร็จก็เป็นเรื่องดีที่รู้ว่ามีเครื่องมืออื่นที่สามารถทำทุกอย่างที่ grep ทำได้ แต่มากขึ้นและง่ายขึ้น
คริส

3
@ vikingsteve ฉันขอสมมติว่าความต้องการโซลูชัน grep เป็นปัญหา XY ชนิดหนึ่ง
Hagen von Eitzen

30

ด้วย GNU grepคุณสามารถส่งผ่านทั้งสองคำไปgrepแล้วลบบรรทัดที่มีทั้งลวดลาย

$ cat testfile.txt
abc
def
abc def
abc 123 def
1234
5678
1234 def abc
def abc

$ grep -w -e 'abc' -e 'def' testfile.txt | grep -v -e 'abc.*def' -e 'def.*abc'
abc
def

16

ลองด้วย egrep

egrep  'pattern1|pattern2' file | grep -v -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'

3
สามารถเขียนเป็นgrep -e foo -e bar | grep -v -e 'foo.*bar' -e 'bar.*foo'
เกล็

8
นอกจากนี้โปรดทราบจากหน้า grep man: Direct invocation as either egrep or fgrep is deprecated- prefergrep -E
glenn jackman

นั่นไม่ได้อยู่ใน OS @glennjackman ของฉัน
Grump

1
@Grump จริงเหรอ? OS คืออะไร POSIXแม้จะกล่าวถึงว่า grep ควรมี-fและ-eตัวเลือกถึงแม้ว่าจะเก่ากว่าegrepและfgrepจะยังคงได้รับการสนับสนุนต่อไปอีกซักพัก
terdon

1
@terdon, POSIX ไม่ได้ระบุเส้นทางของ POSIX ยูทิลิตี้ อีกครั้งมีมาตรฐานgrep(ที่สนับสนุน-F, -E, -e, -fเป็น POSIX ต้อง) /usr/xpg4/binอยู่ใน สาธารณูปโภคใน/binนั้นเป็นวัตถุโบราณ
Stéphane Chazelas

12

ด้วยgrepการใช้งานที่รองรับการแสดงออกปกติแบบ perl (เช่นpcregrepหรือ GNU หรือ ast-open grep -P) คุณสามารถทำได้ในgrepการเรียกใช้ครั้งเดียวด้วย:

grep -P '^(?=.*pat1)(?!.*pat2)|^(?=.*pat2)(?!.*pat1)'

นั่นคือการหาเส้นที่การแข่งขันpat1แต่ไม่pat2หรือแต่ไม่pat2pat1

(?=...)และ(?!...)มีการมองไปข้างหน้าตามลำดับและมองไปข้างหน้าเชิงลบผู้ประกอบการ ดังนั้นในทางเทคนิคแล้วข้างต้นจะมองหาจุดเริ่มต้นของเรื่อง ( ^) ที่จัดให้ตามด้วย.*pat1และไม่ตามด้วย.*pat2หรือเหมือนกับpat1และpat2ย้อนกลับ

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

grep -P '^(?=.*pat1|())(?(1)(?=.*pat2)|(?!.*pat2))'

(?(1)yespattern|nopattern)จับคู่กับyespatternถ้ากลุ่มการจับภาพ1เซนต์ (ว่างเปล่า()ด้านบน) จับคู่และnopatternอย่างอื่น หากการ()จับคู่นั้นหมายความว่าpat1ไม่ตรงกันดังนั้นเราจึงมองหาpat2(มองในแง่ดีล่วงหน้า) และมองหาไม่ pat2อย่างอื่น (มองในแง่ลบ)

ด้วยsedคุณสามารถเขียนมัน:

sed -ne '/pat1/{/pat2/!p;d;}' -e '/pat2/p'

โซลูชันแรกของคุณล้มเหลวด้วยgrep: the -P option only supports a single patternอย่างน้อยในทุกระบบที่ฉันเข้าถึง +1 สำหรับโซลูชันที่สองของคุณ
Chris

1
@ Chris คุณพูดถูก ที่ดูเหมือนว่าจะเป็นข้อ จำกัด ที่เฉพาะเจาะจงเพื่อ grepGNU pcregrepและ grep ที่เปิดอยู่ไม่มีปัญหานั้น ฉันได้แทนที่ตัวคูณ-eด้วยตัวดำเนินการ RE สำรองดังนั้นมันควรทำงานกับ GNU grepเช่นกันในตอนนี้
Stéphane Chazelas

ใช่มันใช้งานได้ดีตอนนี้
Chris

3

ในแง่บูลีนคุณกำลังมองหา A xor B ซึ่งสามารถเขียนเป็น

(A และไม่ใช่ B)

หรือ

(B และไม่ใช่ A)

เนื่องจากคำถามของคุณไม่ได้กล่าวถึงว่าคุณมีความกังวลกับลำดับของผลลัพธ์ตราบใดที่มีการแสดงบรรทัดที่ตรงกันการขยายบูลีนของ A xor B นั้นค่อนข้างง่ายใน grep:

$ cat << EOF > foo
> a b
> a
> b
> c a
> c b
> b a
> b c
> EOF
$ grep -w 'a' foo | grep -vw 'b'; grep -w 'b' foo | grep -vw 'a';
a
c a
b
c b
b c

1
วิธีนี้ใช้งานได้ แต่มันจะเป็นการเรียงลำดับของไฟล์
Sparhawk

@Sparhawk จริงแม้ว่า "การแย่งชิง" เป็นคำที่รุนแรง ;) จะแสดงรายการการแข่งขัน 'a' ทั้งหมดตามลำดับจากนั้นการแข่งขัน 'b' ทั้งหมดต่อไปตามลำดับ OP ไม่แสดงความสนใจใด ๆ ในการรักษาคำสั่งซื้อเพียงแสดงบรรทัด FAWK ขั้นตอนต่อไปอาจเป็นsort | uniqได้
จิมแอล

โทรอย่างยุติธรรม; ฉันเห็นด้วยภาษาของฉันไม่ถูกต้อง ฉันตั้งใจจะบอกว่าคำสั่งซื้อดั้งเดิมจะเปลี่ยนไป
Sparhawk

1
@Sparhawk ... และฉันแก้ไขในการสังเกตของคุณสำหรับการเปิดเผยแบบเต็ม
จิมแอล

-2

สำหรับตัวอย่างต่อไปนี้:

# Patterns:
#    apple
#    pear

# Example line
line="a_apple_apple_pear_a"

ซึ่งสามารถทำได้อย่างหมดจดด้วยgrep -E, และuniqwc

# Grep for regex pattern, sort as unique, and count the number of lines
result=$(grep -oE 'apple|pear' <<< $line | sort -u | wc -l)

หากgrepมีการรวบรวมด้วยการแสดงออกปกติ Perl แล้วคุณสามารถจับคู่ในการเกิดขึ้นครั้งสุดท้ายแทนที่จะต้องไปป์uniq:

# Grep for regex pattern and count the number of lines
result=$(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l)

ส่งออกผลลัพธ์:

# Only one of the words exists if the result is < 2
((result > 0)) &&
   if (($result < 2)); then
      echo Only one word matched
   else
      echo Both words matched
   fi

หนึ่งซับ:

(($(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l) == 1)) && echo Only one word matched

หากคุณไม่ต้องการเขียนโค้ดรูปแบบที่ยากการประกอบมันด้วยชุดองค์ประกอบที่หลากหลายสามารถเป็นไปโดยอัตโนมัติด้วยฟังก์ชั่น

สิ่งนี้สามารถทำได้โดยกำเนิดใน Bash เป็นฟังก์ชันโดยไม่มีไพพ์หรือกระบวนการเพิ่มเติม แต่จะเกี่ยวข้องมากกว่าและอาจอยู่นอกขอบเขตของคำถามของคุณ


(1) ฉันสงสัยว่าเมื่อมีคนต้องการคำตอบโดยใช้ Perl ปกติ หากคุณมุ่งเน้นที่ส่วนของโพสต์ของคุณและอธิบายว่ามันทำงานอย่างไรนี่อาจเป็นคำตอบที่ดี (2) แต่ฉันเกรงว่าส่วนที่เหลือจะไม่ดีนัก คำถามบอกว่า“ แสดงเฉพาะบรรทัดที่มีคำใดคำหนึ่งในสองคำ” (เน้นที่เพิ่ม) ถ้าผลการควรจะเป็นเส้น ,แล้วมันยืนเพื่อเหตุผลที่ทำให้การป้อนข้อมูลที่ยังต้องมีหลายสาย   แต่วิธีการของคุณใช้งานได้เฉพาะเมื่อดูที่บรรทัดเดียวเท่านั้น … (ต่อ)
G-Man พูดว่า 'Reinstate Monica'

(ต่อ) ตัวอย่างเช่นหากอินพุตมีบรรทัดBig apple\nและpear-shaped\nดังนั้นเอาต์พุตควรมีทั้งสองบรรทัด โซลูชันของคุณจะได้รับการนับ 2; รุ่นยาวจะรายงาน“ ทั้งสองคำที่ตรงกัน” (ซึ่งเป็นคำตอบสำหรับคำถามที่ผิด) และรุ่นสั้นจะไม่พูดอะไรเลย (3) ข้อเสนอแนะ: การใช้-oที่นี่เป็นความคิดที่ไม่ดีจริงๆเพราะมันจะซ่อนบรรทัดที่มีการจับคู่ไว้ดังนั้นคุณจึงไม่สามารถมองเห็นเมื่อทั้งสองคำปรากฏในบรรทัดเดียวกัน … (ต่อ)
G-Man พูดว่า 'Reinstate Monica'

(ต่อ) ... (4) บรรทัดล่าง: การใช้uniq/ sort -uและการแสดงออกปกติของ Perl แฟนซีเพื่อให้ตรงกับการเกิดขึ้นครั้งสุดท้ายในแต่ละบรรทัดไม่ได้เพิ่มคำตอบที่เป็นประโยชน์สำหรับคำถามนี้ แต่แม้ว่าพวกเขาจะทำเช่นนั้นก็ยังคงเป็นคำตอบที่ไม่ดีเพราะคุณไม่ได้อธิบายว่าพวกเขามีส่วนช่วยตอบคำถามอย่างไร (ดูคำตอบของStéphane Chazelasเพื่อเป็นตัวอย่างของคำอธิบายที่ดี)
G-Man พูดว่า 'Reinstate Monica'

OP บอกว่าพวกเขาต้องการ "แสดงเฉพาะบรรทัดที่มีคำใดคำหนึ่งในสองคำ" ซึ่งหมายความว่าแต่ละบรรทัดต้องได้รับการประเมินด้วยตนเอง ฉันไม่เห็นสาเหตุที่คุณรู้สึกว่าสิ่งนี้ไม่ตอบคำถาม โปรดระบุตัวอย่างการป้อนข้อมูลที่คุณรู้สึกว่าจะล้มเหลว
Zhro

โอ้ว่าสิ่งที่คุณหมาย? “อ่านอินพุตสายในเวลาและดำเนินการทั้งสองหรือสามคำสั่งทุกบรรทัด “? (1) มันไม่ชัดเจนอย่างเจ็บปวดว่าเป็นสิ่งที่คุณหมายถึง (2) มันไม่มีประสิทธิภาพอย่างเจ็บปวด คำตอบสี่ข้อก่อนที่คุณจะแสดงวิธีจัดการไฟล์ทั้งหมดด้วยคำสั่งสองสามคำ (หนึ่งสองหรือสี่) และคุณต้องการรันคำสั่ง3 ×  nสำหรับอินพุตบรรทัดnบรรทัด? แม้ว่าจะใช้งานได้ แต่จะได้รับการลงคะแนนสำหรับการดำเนินการที่มีราคาแพงโดยไม่จำเป็น (3) เมื่อมีความเสี่ยงในการแตกผมก็ยังไม่สามารถแสดงเส้นที่เหมาะสมได้
G-Man กล่าวว่า 'Reinstate Monica'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.