ฉันจะ“ grep” รูปแบบข้ามหลายบรรทัดได้อย่างไร?


24

ดูเหมือนว่าฉันใช้ผิดวัตถุประสงค์grep/ egrep.

ฉันพยายามค้นหาสตริงในหลายบรรทัดและไม่สามารถหาคู่ที่ตรงกันในขณะที่ฉันรู้ว่าสิ่งที่ฉันกำลังมองหาควรจับคู่ แต่เดิมฉันคิดว่า regexes ของฉันผิด แต่ในที่สุดฉันก็อ่านว่าเครื่องมือเหล่านี้ทำงานต่อบรรทัด (เช่นเดียวกับ regexes ของฉันมันช่างน่ารำคาญเหลือเกิน

ดังนั้นเครื่องมือใดที่จะใช้ในการค้นหารูปแบบข้ามหลายบรรทัด?


เป็นไปได้ที่ซ้ำกันของรูปแบบ Multiline โดยใช้ sed, awk หรือ grep
Ciro Santilli

1
@CiroSantilli - ฉันไม่คิดว่าคำถามนี้และคำถามที่คุณเชื่อมโยงนั้นซ้ำซ้อน อื่น ๆ Q จะขอวิธีการที่คุณต้องการทำหลายสายการแข่งขันรูปแบบ (คือสิ่งที่ควรเครื่องมือ / ฉันสามารถใช้เพื่อทำเช่นนี้) grepในขณะที่หนึ่งนี้จะถามถึงวิธีการทำเช่นนี้กับ พวกมันมีความสัมพันธ์แน่นแฟ้น
slm

@sim กรณีเหล่านี้ยากที่จะตัดสินใจ: ฉันเห็นประเด็นของคุณ ฉันคิดว่ากรณีนี้ดีกว่าเพราะผู้ใช้บอกว่าใช้"grep"คำกริยา "ถึง grep" และคำตอบยอดนิยมซึ่งรวมถึงการยอมรับไม่ใช้ grep
Ciro Santilli i 事件中心法轮功六四事件

คำตอบ:


24

นี่คือสิ่งsedที่จะทำให้คุณมีgrepพฤติกรรมคล้ายกับหลาย ๆ บรรทัด:

sed -n '/foo/{:start /bar/!{N;b start};/your_regex/p}' your_file

มันทำงานอย่างไร

  • -n ไม่แสดงพฤติกรรมเริ่มต้นของการพิมพ์ทุกบรรทัด
  • /foo/{}สั่งให้มันจับคู่fooและทำสิ่งที่อยู่ภายใน squigglies กับสายการจับคู่ แทนที่fooด้วยส่วนเริ่มต้นของรูปแบบ
  • :start เป็นฉลากแยกย่อยเพื่อช่วยให้เราวนซ้ำจนกว่าเราจะหาจุดสิ้นสุดของ regex ของเรา
  • /bar/!{}จะดำเนินการอะไรใน squigglies barเพื่อเส้นที่ไม่ตรงกับ แทนที่barด้วยส่วนท้ายของรูปแบบ
  • Nผนวกบรรทัดถัดไปเข้ากับบัฟเฟอร์ที่ใช้งานอยู่ ( sedเรียกพื้นที่นี้ว่า pattern)
  • b startจะไม่มีเงื่อนไขสาขาไปป้ายที่เราสร้างไว้ก่อนหน้าเพื่อให้ท้ายบรรทัดถัดไปเป็นเวลานานเป็นพื้นที่รูปแบบไม่ได้มีstartbar
  • /your_regex/pyour_regexพิมพ์พื้นที่รูปแบบว่ามันตรง คุณควรแทนที่your_regexด้วยนิพจน์ทั้งหมดที่คุณต้องการจับคู่ข้ามหลายบรรทัด

1
+1 เพิ่มลงใน toolikt! ขอบคุณ
wmorrison365

หมายเหตุ: สำหรับ MacOS สิ่งนี้ให้sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Stan James

1
ได้รับsed: unterminated {ข้อผิดพลาด
ไม่มี

@Nomaed Shot ในที่มืดที่นี่ แต่ regex ของคุณมีอักขระ "{" อยู่หรือไม่? ถ้าเป็นเช่นนั้นคุณจะต้องหลบหลีกการหลบหลีก
โจเซฟอาร์

1
@ ไม่มีใครดูเหมือนว่าจะต้องเกี่ยวข้องกับความแตกต่างระหว่างsedการใช้งาน ฉันพยายามที่จะปฏิบัติตามคำแนะนำในคำตอบนั้นเพื่อให้สอดคล้องกับมาตรฐานสคริปต์ข้างต้น แต่มันบอกฉันว่า "เริ่มต้น" เป็นป้ายกำกับที่ไม่ได้กำหนด ดังนั้นฉันไม่แน่ใจว่าสิ่งนี้สามารถทำได้ในลักษณะที่เป็นไปตามมาตรฐานหรือไม่ หากคุณจัดการได้โปรดแก้ไขคำตอบของฉัน
Joseph R.

19

ฉันมักจะใช้เครื่องมือที่เรียกว่าpcregrepที่สามารถติดตั้งในส่วนของรสชาติลินุกซ์ใช้หรือyumapt

สำหรับเช่น

สมมติว่าคุณมีชื่อไฟล์ที่testfileมีเนื้อหา

abc blah
blah blah
def blah
blah blah

คุณสามารถเรียกใช้คำสั่งต่อไปนี้:

$ pcregrep -M  'abc.*(\n|.)*def' testfile

เพื่อทำการจับคู่รูปแบบข้ามหลายบรรทัด

นอกจากนี้คุณสามารถทำเช่นเดียวกันกับsedเช่นกัน

$ sed -e '/abc/,/def/!d' testfile

5

นี่เป็นวิธีที่ง่ายกว่าโดยใช้ Perl:

perl -e '$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m' file

หรือ (เนื่องจาก JosephR ใช้sedเส้นทางฉันจะขโมยคำแนะนำของเขาอย่างไร้ยางอาย)

perl -n000e 'print $& while /^foo.*\nbar.*\n/mg' file

คำอธิบาย

$f=join("",<>);: นี้อ่านไฟล์ทั้งหมดและบันทึกเนื้อหาของมัน (การขึ้นบรรทัดใหม่และทุก) $fลงในตัวแปร จากนั้นเราพยายามที่จะจับคู่foo\nbar.*\nและพิมพ์ถ้ามันตรงกัน (ตัวแปรพิเศษ$&ถือพบการแข่งขันครั้งสุดท้าย) ///mเป็นสิ่งจำเป็นที่จะทำให้การแข่งขันการแสดงออกปกติข้ามบรรทัดใหม่

-0ชุดคั่นบันทึกการป้อนข้อมูล การตั้งค่านี้เพื่อ00เปิดใช้งาน 'โหมดย่อหน้า' โดยที่ Perl จะใช้ newlines ต่อเนื่อง ( \n\n) เป็นตัวคั่นเร็กคอร์ด ในกรณีที่ไม่มีการขึ้นบรรทัดใหม่ติดต่อกันไฟล์ทั้งหมดจะถูกอ่าน (slurped) ในครั้งเดียว

คำเตือน:

อย่าได้ทำเช่นนี้สำหรับไฟล์ขนาดใหญ่ก็จะโหลดไฟล์ทั้งหมดลงในหน่วยความจำและที่อาจจะมีปัญหา


2

วิธีหนึ่งในการทำเช่นนี้คือกับ Perl เช่นนี่คือเนื้อหาของไฟล์ชื่อfoo:

foo line 1
bar line 2
foo
foo
foo line 5
foo
bar line 6

ตอนนี้นี่คือ Perl บางส่วนที่จะจับคู่กับบรรทัดใด ๆ ที่ขึ้นต้นด้วย foo ตามด้วยบรรทัดใด ๆ ที่ขึ้นต้นด้วย bar:

cat foo | perl -e 'while(<>){$all .= $_}
  while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) {
  print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m;
}'

Perl แบ่งออกเป็น:

  • while(<>){$all .= $_} สิ่งนี้จะโหลดอินพุตมาตรฐานทั้งหมดไปยังตัวแปร $all
  • while($all =~ในขณะที่ตัวแปรallมีการแสดงออกปกติ ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/mregex: foo ที่จุดเริ่มต้นของบรรทัดตามด้วยจำนวนอักขระที่ไม่ใช่บรรทัดใหม่ตามด้วยบรรทัดใหม่ตามด้วย "bar" ทันทีและส่วนที่เหลือของบรรทัดที่มีแถบอยู่ /mในตอนท้ายของ regex หมายถึง "การจับคู่ข้ามหลายบรรทัด"
  • print $1 พิมพ์ส่วนของ regex ที่อยู่ในวงเล็บ (ในกรณีนี้คือนิพจน์ทั่วไปทั้งหมด)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m ลบการจับคู่ครั้งแรกสำหรับ regex เพื่อให้เราสามารถจับคู่ regex หลายกรณีในไฟล์ที่เป็นปัญหา

และผลลัพธ์:

foo line 1
bar line 2
foo
bar line 6

3
เพียงแค่แวะบอกว่า Perl ของคุณสามารถย่อให้สั้นลงได้มากขึ้น:perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Joseph R.

2

ร่อนทางเลือก grep รองรับการจับคู่หลายบรรทัด(ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้เขียน)

สมมติว่าtestfileมี:

<หนังสือ>
  <title> Lorem Ipsum </title>
  <คำอธิบาย> Lorem ipsum dolor sit amet, consectetur
  adipiscing elit, sed ทำ eiusmod ชั่วขณะที่เกิดขึ้น
  labore และ dolore magna aliqua </description>
</ หนังสือ>


sift -m '<description>.*?</description>' (แสดงบรรทัดที่มีคำอธิบาย)

ผล:

testfile: <description> Lorem ipsum dolor sit amet, consectetur
testfile: adipiscing elit, ทำ eiusmod ชั่วขณะชั่วคราว
testfile: labore และ dolore magna aliqua </description>


sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (แยกและฟอร์แมตคำอธิบาย)

ผล:

คำอธิบาย = "Lorem ipsum dolor sit amet, consectetur
  adipiscing elit, sed ทำ eiusmod ชั่วขณะที่เกิดขึ้น
  Labore et dolore magna aliqua "

1
เครื่องมือที่ดีมาก ขอแสดงความยินดี! พยายามรวมไว้ในการแจกแจงเช่น Ubuntu
Lourenco

2

เพียงแค่ grep ปกติซึ่งรองรับPerl-regexpพารามิเตอร์Pจะทำงานนี้

$ echo 'abc blah
blah blah
def blah
blah blah' | grep -oPz  '(?s)abc.*?def'
abc blah
blah blah
def

(?s) เรียกว่าตัวดัดแปลง DOTALL ซึ่งทำให้จุดใน regex ของคุณจับคู่ไม่เพียง แต่ตัวอักษร แต่ยังแบ่งบรรทัด


เมื่อฉันลองวิธีนี้ผลลัพธ์จะไม่จบที่ 'def' แต่ไปที่ท้ายไฟล์ 'blah'
buckley

บางที grep ของคุณไม่รองรับ-Pตัวเลือก
Avinash Raj

1

ฉันแก้ไขอันนี้ให้ฉันโดยใช้ grep และตัวเลือก -A กับ grep อื่น

grep first_line_word -A 1 testfile | grep second_line_word

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


นามแฝง grepp = 'grep --color = auto -B10 -A20 -i' จากนั้นให้ cat somefile | grepp blah | grepp foo grepp bar ... ใช่แล้ว -A และ -B นั้นมีประโยชน์มาก ... คุณมีคำตอบที่ดีที่สุด
Scott Stensland

1

สมมติว่าเรามีไฟล์test.txtซึ่งมี:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

รหัสต่อไปนี้สามารถใช้ได้:

sed -n '/foo/,/bar/p' test.txt

สำหรับผลลัพธ์ต่อไปนี้:

foo
here
is the
text
to keep between the 2 patterns
bar

1

ถ้าเราต้องการที่จะได้รับข้อความระหว่าง 2 รูปแบบไม่รวมตัวเอง

สมมติว่าเรามีไฟล์test.txtซึ่งมี:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

รหัสต่อไปนี้สามารถใช้ได้:

 sed -n '/foo/{
 n
 b gotoloop
 :loop
 N
 :gotoloop
 /bar/!{
 h
 b loop
 }
 /bar/{
 g
 p
 }
 }' test.txt

สำหรับผลลัพธ์ต่อไปนี้:

here
is the
text
to keep between the 2 patterns

มันทำงานอย่างไรมาทำให้เป็นขั้นเป็นตอน

  1. /foo/{ ถูกเรียกใช้เมื่อบรรทัดมี "foo"
  2. n แทนที่พื้นที่รูปแบบด้วยบรรทัดถัดไปเช่นคำว่า "ที่นี่"
  3. b gotoloop สาขาไปยังป้ายกำกับ "gotoloop"
  4. :gotoloop กำหนดฉลาก "gotoloop"
  5. /bar/!{ หากรูปแบบไม่มี "bar"
  6. h แทนที่พื้นที่พักด้วยรูปแบบดังนั้น "ที่นี่" จะถูกบันทึกไว้ในพื้นที่พัก
  7. b loop สาขาไปที่ป้ายกำกับ "ห่วง"
  8. :loop กำหนดฉลาก "ห่วง"
  9. N ผนวกรูปแบบเข้ากับพื้นที่พัก
    ตอนนี้มีพื้นที่ค้างไว้:
    "ที่นี่"
    "คือ"
  10. :gotoloop ตอนนี้เราอยู่ที่ขั้นตอนที่ 4 และวนซ้ำจนกว่าบรรทัดจะมี "bar"
  11. /bar/ วนรอบเสร็จสิ้นพบ "แถบ" เป็นพื้นที่รูปแบบ
  12. g พื้นที่รูปแบบจะถูกแทนที่ด้วยพื้นที่พักที่มีบรรทัดทั้งหมดระหว่าง "foo" และ "บาร์" ที่ได้รับการบันทึกในระหว่างการวนรอบหลัก
  13. p คัดลอกพื้นที่รูปแบบไปยังเอาต์พุตมาตรฐาน

เสร็จแล้ว!


ทำได้ดี +1 ฉันมักจะหลีกเลี่ยงการใช้คำสั่งเหล่านี้โดยการขึ้นบรรทัดใหม่ใน SOH และดำเนินการคำสั่ง sed ปกติแล้วแทนที่บรรทัดใหม่
A.Danischewski
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.