Regex lookahead สำหรับ 'not ตามด้วย' ใน grep


104

ฉันพยายาม grep สำหรับทุกกรณีที่Ui\.ไม่ได้ตามด้วยLineหรือแม้แต่ตัวอักษรL

อะไรคือวิธีที่เหมาะสมในการเขียนนิพจน์ทั่วไปสำหรับการค้นหาอินสแตนซ์ทั้งหมดของสตริงเฉพาะที่ไม่ตามด้วยสตริงอื่น

ใช้ Lookaheads

grep "Ui\.(?!L)" *
bash: !L: event not found


grep "Ui\.(?!(Line))" *
nothing

5
regex สายพันธุ์ย่อยใด - PCRE, ERE, BRE, grep, ed, sed, perl, python, Java, C, ... ?
Jonathan Leffler

4
นอกจากนี้ "ไม่พบเหตุการณ์" มาจากการใช้การขยายประวัติ คุณอาจต้องการปิดการขยายประวัติหากคุณไม่เคยใช้งานและบางครั้งก็ต้องการใช้เครื่องหมายอัศเจรีย์ในคำสั่งแบบโต้ตอบของคุณ set +o histexpandใน Bash หรือset +HYMMV
tripleee

12
ฉันยังมีปัญหาการขยายประวัติ ฉันคิดว่าฉันแก้ไขได้ง่ายๆโดยเปลี่ยนไปใช้เครื่องหมายคำพูดเดี่ยวดังนั้นเชลล์จะไม่พยายามที่จะทำลายอาร์กิวเมนต์
Coderer

@Coderer ที่แก้ไขปัญหาของฉันเช่นกัน ขอบคุณ.
NHDaly

คำตอบ:


151

lookahead grepเชิงลบซึ่งเป็นสิ่งที่คุณหลังจากต้องใช้เครื่องมือที่มีประสิทธิภาพมากขึ้นกว่ามาตรฐาน คุณต้องมี grep ที่เปิดใช้งาน PCRE

หากคุณมี GNU grepเวอร์ชันปัจจุบันรองรับตัวเลือก-Pหรือ--perl-regexpจากนั้นคุณสามารถใช้ regex ที่คุณต้องการได้

หากคุณไม่ได้ (รุ่นล่าสุดพอ) GNU แล้วพิจารณารับgrepack


37
ฉันค่อนข้างแน่ใจว่าปัญหาในกรณีนี้คือในการทุบตีคุณควรใช้เครื่องหมายคำพูดเดี่ยวไม่ใช่เครื่องหมายคำพูดคู่ดังนั้นจึงไม่ถือว่า!เป็นอักขระพิเศษ
NHDaly

(ดูด้านล่างสำหรับคำตอบของฉันที่อธิบายอย่างนั้น)
NHDaly

4
คำตอบที่ได้รับการยืนยันและถูกต้องควรรวมคำตอบนี้เข้ากับความคิดเห็นของ @NHDaly ตัวอย่างเช่นคำสั่งนี้ใช้ได้กับฉัน: grep -P '^. * มี ((?! but_not_this).) * $' * .log. *> "D: \ temp \ result.out"
wangf

3
สำหรับผู้ที่-Pไม่ได้รับการสนับสนุนผลท่อลองอีกครั้งเพื่ออดีต:grep --invert-match git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'อย่าลืมโหวตคำตอบของ @Vinicius Ottoni
Daniel Sokolowski

@wangf ฉันใช้ Bash ภายใต้ Cygwin และเมื่อฉันเปลี่ยนเป็นเครื่องหมายคำพูดเดี่ยวฉันยังคงได้รับข้อผิดพลาด "ไม่พบเหตุการณ์"
SSilk

41

คำตอบสำหรับส่วนหนึ่งของปัญหาของคุณอยู่ที่นี่และ ack จะทำงานในลักษณะเดียวกัน: Ack & negative lookahead ให้ข้อผิดพลาด

คุณกำลังใช้เครื่องหมายคำพูดคู่สำหรับ grep ซึ่งอนุญาตให้ bash "ตีความ!เป็นคำสั่งขยายประวัติ"

คุณต้องห่อรูปแบบของคุณเป็นคำพูดเดียว: grep 'Ui\.(?!L)' *

อย่างไรก็ตามโปรดดูคำตอบของ @JonathanLefflerเพื่อแก้ไขปัญหาเกี่ยวกับผู้มองเชิงลบในมาตรฐานgrep!


คุณกำลังสับสนฟังก์ชันส่วนขยายของ GNU grepกับฟังก์ชันการทำงานของมาตรฐานgrepโดยที่มาตรฐานgrepคือ POSIX สิ่งที่คุณพูดก็เป็นความจริงเช่นกัน - ฉันเรียกใช้ Bash โดยปิดการใช้งาน C-shell barbarisms (เพราะถ้าฉันต้องการ C เชลล์ฉันจะใช้อันนี้ แต่ฉันไม่ต้องการ) ดังนั้น!สิ่งต่างๆจึงไม่มีผลกับฉัน - แต่เพื่อให้ได้ lookaheads grepลบคุณจำเป็นที่ไม่ได้มาตรฐาน
Jonathan Leffler

1
@JonathanLeffler ขอบคุณสำหรับการชี้แจง; ฉันคิดว่าคุณคิดถูกแล้วที่ต้องการคำตอบทั้งสองข้อของเราเพื่อจัดการกับอาการทั้งหมดของ OP ขอบคุณ.
NHDaly

11

คุณอาจไม่สามารถดำเนินการ lookahead เชิงลบมาตรฐานโดยใช้ grep ได้ แต่โดยปกติแล้วคุณควรได้รับพฤติกรรมที่เทียบเท่ากันโดยใช้สวิตช์ "ผกผัน" '-v' การใช้นั้นคุณสามารถสร้างนิพจน์ทั่วไปสำหรับส่วนเติมเต็มของสิ่งที่คุณต้องการจับคู่แล้วต่อท่อผ่าน 2 greps

สำหรับ regex ที่เป็นปัญหาคุณอาจทำสิ่งที่ต้องการ

grep 'Ui\.' * | grep -v 'Ui\.L'

นั่นจะไม่รวมสิ่งต่างๆมากขึ้นเช่นหากบรรทัดมี Ui.Line และ Ui ที่ไม่มี. Line
nafg

1
(ใช่นั่นเป็นเหตุผลที่ฉันไม่กำหนดอย่างเคร่งครัดนี่เป็นเพียงการแก้ปัญหาส่วนสำคัญที่นำทางผู้คนไปสู่ปัญหานี้ไม่มีอะไรเพิ่มเติม)
Karel Tucek

4

หากคุณจำเป็นต้องใช้การดำเนิน regex ที่ไม่สนับสนุน lookaheads เชิงลบและคุณไม่คิดที่ตรงกับตัวอักษรพิเศษ (s) * แล้วคุณสามารถใช้เรียนเมื่อตะกี้ตัวอักษร[^L] , การสับเปลี่ยน|และจุดสิ้นสุดของสตริงสมอ$

ในกรณีของคุณgrep 'Ui\.\([^L]\|$\)' *ไม่ได้ผล

  • Ui\. ตรงกับสตริงที่คุณสนใจ

  • \([^L]\|$\)ตรงกับตัวละครอื่น ๆ เดียวกว่าLหรือมันตรงกับจุดสิ้นสุดของบรรทัดนี้หรือ[^L]$

หากคุณต้องการยกเว้นอักขระมากกว่าหนึ่งตัวคุณก็ต้องเพิ่มการสลับและการปฏิเสธให้มากขึ้น หากต้องการค้นหาaไม่ตามด้วยbc:

grep 'a\(\([^b]\|$\)\|\(b\([^c]\|$\)\)\)' *

ซึ่งเป็นอย่างใดอย่างหนึ่ง ( aตามด้วย not bหรือตามด้วยท้ายบรรทัด: aแล้ว[^b]หรือ$) หรือ ( aตามด้วยbซึ่งตามด้วย not cหรือตามด้วยท้ายบรรทัด: athen b, then [^c]or $.

นิพจน์ประเภทนี้ค่อนข้างเทอะทะและเกิดข้อผิดพลาดแม้กระทั่งสตริงสั้น ๆ คุณสามารถเขียนบางอย่างเพื่อสร้างนิพจน์ให้กับคุณได้ แต่อาจจะง่ายกว่าถ้าใช้ regex ที่สนับสนุนการมองไปข้างหน้าเชิงลบ

* หากการใช้งานของคุณรองรับกลุ่มที่ไม่จับภาพคุณสามารถหลีกเลี่ยงการจับอักขระพิเศษได้


1

หาก grep ของคุณไม่รองรับ -P หรือ --perl-regexp และคุณสามารถติดตั้ง grep ที่เปิดใช้งาน PCRE เช่น "pcregrep" ได้มากกว่าที่จะไม่ต้องใช้ตัวเลือกบรรทัดคำสั่งเช่น GNU grep เพื่อยอมรับ Perl ที่เข้ากันได้กับปกติ นิพจน์คุณเพียงแค่เรียกใช้

pcregrep "Ui\.(?!Line)"

คุณไม่จำเป็นต้องมีกลุ่มที่ซ้อนกันสำหรับ "Line" ดังในตัวอย่างของคุณ "Ui. (?! (Line))" - กลุ่มภายนอกก็เพียงพอแล้วดังที่แสดงไว้ด้านบน

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

ipset list | pcregrep "packets(?! 0 )"

หากคุณชอบนิพจน์ทั่วไปที่เข้ากันได้กับ perl และมี perl แต่ไม่มี pcregrep หรือ grep ของคุณไม่รองรับ --perl-regexp คุณสามารถใช้สคริปต์ perl แบบบรรทัดเดียวที่ทำงานในลักษณะเดียวกันเช่น grep:

perl -e "while (<>) {if (/Ui\.(?!Lines)/){print;};}"

Perl ยอมรับ stdin ในลักษณะเดียวกับ grep เช่น

ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.