จะใช้ sed, awk หรือ gawk เพื่อพิมพ์เฉพาะสิ่งที่จับคู่ได้อย่างไร?


101

ฉันเห็นตัวอย่างและหน้าคนมากมายเกี่ยวกับวิธีการทำสิ่งต่างๆเช่นการค้นหาและแทนที่โดยใช้ sed, awk หรือ gawk

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

ตัวอย่างนิพจน์ทั่วไป:

.*abc([0-9]+)xyz.*

ตัวอย่างไฟล์อินพุต:

a
b
c
abc12345xyz
a
b
c

ง่ายเหมือนเสียงนี้ฉันไม่สามารถหาวิธีเรียก sed / awk / gawk ได้อย่างถูกต้อง สิ่งที่ฉันหวังว่าจะทำคือจากในสคริปต์ทุบตีของฉันมี:

myvalue=$( sed <...something...> input.txt )

สิ่งที่ฉันได้ลอง ได้แก่ :

sed -e 's/.*([0-9]).*/\\1/g' example.txt # extracts the entire input file
sed -n 's/.*([0-9]).*/\\1/g' example.txt # extracts nothing

10
ว้าว ... คนโหวตคำถามนี้ลดลง -1? เป็นคำถามที่ไม่เหมาะสมจริงๆหรือ?
Stéphane

ดูเหมือนว่าเหมาะสมอย่างยิ่งการใช้ Regex และยูทิลิตี้บรรทัดคำสั่งที่มีประสิทธิภาพเช่น sed / awk หรือตัวแก้ไขใด ๆ เช่น vi, emacs หรือ teco อาจเป็นเหมือนการเขียนโปรแกรมมากกว่าการใช้แอปพลิเคชัน ol บางตัว IMO นี้เป็นของ SO มากกว่า SU
เผยแพร่

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

คำตอบ:


43

ของฉันsed(Mac OS X) +ไม่ได้ทำงานร่วมกับ ฉันลอง*แทนและเพิ่มpแท็กสำหรับการจับคู่การพิมพ์:

sed -n 's/^.*abc\([0-9]*\)xyz.*$/\1/p' example.txt

สำหรับการจับคู่อักขระตัวเลขอย่างน้อยหนึ่งตัวโดยไม่มี+ฉันจะใช้:

sed -n 's/^.*abc\([0-9][0-9]*\)xyz.*$/\1/p' example.txt

ขอบคุณสิ่งนี้ใช้ได้ผลสำหรับฉันเช่นกันเมื่อฉันใช้ * แทน +
Stéphane

2
... และตัวเลือก "p" เพื่อพิมพ์การจับคู่ซึ่งฉันไม่รู้เหมือนกัน ขอบคุณอีกครั้ง.
Stéphane

2
ฉันต้องหลบหนี+และจากนั้นมันก็ใช้ได้ผลสำหรับฉัน:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบเพิ่มเติม

3
นั่นเป็นเพราะคุณไม่ได้ใช้รูปแบบ RE สมัยใหม่ดังนั้น + จึงเป็นอักขระมาตรฐานและคุณควรแสดงออกด้วยไวยากรณ์ {,} คุณสามารถเพิ่มตัวเลือก use -E sed เพื่อทริกเกอร์รูปแบบ RE ที่ทันสมัย ตรวจสอบรูปแบบใหม่ (7) โดยเฉพาะย่อหน้าสุดท้ายของ DESCRIPTION developer.apple.com/library/mac/#documentation/Darwin/Reference/…
anddam

35

คุณสามารถใช้ sed เพื่อทำสิ่งนี้

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
  • -n อย่าพิมพ์บรรทัดผลลัพธ์
  • -rนี้จะทำให้มันเพื่อให้คุณไม่ได้หลบหนีการจับกุมกลุ่ม ()parens
  • \1 จับคู่จับกลุ่ม
  • /g การแข่งขันระดับโลก
  • /p พิมพ์ผลลัพธ์

ฉันเขียนเครื่องมือสำหรับตัวเองที่ทำให้สิ่งนี้ง่ายขึ้น

rip 'abc(\d+)xyz' '$1'

3
นี่เป็นคำตอบที่ดีที่สุดและอธิบายได้ดีที่สุดจนถึงตอนนี้!
Nik Reiman

ด้วยคำอธิบายบางประการจะเป็นการดีกว่าที่จะทำความเข้าใจว่าเกิดอะไรขึ้นกับปัญหาของเรา ขอบคุณ !
r4phG

17

ฉันใช้perlเพื่อทำให้ง่ายขึ้นสำหรับตัวเอง เช่น

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/'

สิ่งนี้จะเรียกใช้ Perl -nตัวเลือกสั่งให้ Perl อ่านทีละบรรทัดจาก STDIN และรันโค้ด -eตัวเลือกระบุคำแนะนำในการทำงาน

คำสั่งเรียกใช้ regexp ในบรรทัดที่อ่านและหากตรงกันจะพิมพ์เนื้อหาของวงเล็บชุดแรก ( $1)

คุณสามารถทำได้หลายชื่อไฟล์ในตอนท้ายด้วย เช่น

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/' example1.txt example2.txt


ขอบคุณ แต่เราไม่สามารถเข้าถึง perl ได้ซึ่งเป็นสาเหตุที่ฉันถามเกี่ยวกับ sed / awk / gawk
Stéphane

5

หากเวอร์ชันของคุณgrepรองรับคุณสามารถใช้-oตัวเลือกเพื่อพิมพ์เฉพาะส่วนของบรรทัดใดก็ได้ที่ตรงกับ regexp ของคุณ

ถ้าไม่เช่นนั้นนี่คือสิ่งที่ดีที่สุดที่sedฉันสามารถทำได้:

sed -e '/[0-9]/!d' -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

... ซึ่งลบ / ข้ามโดยไม่มีตัวเลขและสำหรับบรรทัดที่เหลือจะลบอักขระที่ไม่ใช่ตัวเลขนำหน้าและต่อท้ายทั้งหมด (ฉันคาดเดาได้ว่าคุณตั้งใจจะดึงหมายเลขออกจากแต่ละบรรทัดที่มีหนึ่งบรรทัด)

ปัญหาเกี่ยวกับสิ่งที่ต้องการ:

sed -e 's/.*\([0-9]*\).*/&/' 

.... หรือ

sed -e 's/.*\([0-9]*\).*/\1/'

... คือว่า sedรองรับการจับคู่แบบ "โลภ" เท่านั้น ... ดังนั้นข้อแรก * จะตรงกับส่วนที่เหลือของบรรทัด เว้นแต่เราจะสามารถใช้คลาสอักขระลบเพื่อให้ได้การจับคู่แบบไม่โลภ ... หรือเวอร์ชันที่sedเข้ากันได้กับ Perl หรือส่วนขยายอื่น ๆ กับ regexes เราไม่สามารถแยกรูปแบบที่ตรงกับช่องว่างของรูปแบบได้ (เส้น ).


คุณสามารถรวมสองsedคำสั่งของคุณด้วยวิธีนี้:sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p'
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

ก่อนหน้านี้ไม่รู้เรื่อง -o option บน grep ดีที่รู้. แต่จะพิมพ์การจับคู่ทั้งหมดไม่ใช่ "(... )" ดังนั้นหากคุณจับคู่กับ "abc ([[: digit:]] +) xyz" คุณจะได้รับ "abc" และ "xyz" รวมทั้งตัวเลข
Stéphane

ขอบคุณที่เตือนฉันgrep -o! ฉันพยายามทำสิ่งนี้ด้วยsedและต่อสู้กับความต้องการของฉันในการค้นหาการแข่งขันหลายรายการในบางบรรทัด ทางออกของฉันคือstackoverflow.com/a/58308239/117471
Bruno Bronosky

3

คุณสามารถใช้awkกับmatch()การเข้าถึงกลุ่มจับ:

$ awk 'match($0, /abc([0-9]+)xyz/, matches) {print matches[1]}' file
12345

abc[0-9]+xyzนี้พยายามที่จะตรงกับรูปแบบ ถ้ามันไม่เป็นเช่นนั้นก็เก็บชิ้นในอาร์เรย์ซึ่งรายการแรกเป็นบล็อกmatches [0-9]+เนื่องจากmatch() ส่งคืนตำแหน่งอักขระหรือดัชนีที่สตริงย่อยนั้นเริ่มต้น (1 ถ้าเริ่มต้นที่จุดเริ่มต้นของสตริง)จะทำให้เกิดการprintดำเนินการ


ด้วยgrepคุณสามารถใช้การมองข้างหลังและมองไปข้างหน้า:

$ grep -oP '(?<=abc)[0-9]+(?=xyz)' file
12345

$ grep -oP 'abc\K[0-9]+(?=xyz)' file
12345

การตรวจสอบนี้รูปแบบ[0-9]+เมื่อมันเกิดขึ้นภายในabcและxyzและเพียงแค่พิมพ์ตัวเลข


2

perl เป็นไวยากรณ์ที่สะอาดที่สุด แต่ถ้าคุณไม่มี perl (ไม่เสมอไปฉันเข้าใจ) วิธีเดียวที่จะใช้ gawk และส่วนประกอบของ regex คือการใช้คุณสมบัติ gensub

gawk '/abc[0-9]+xyz/ { print gensub(/.*([0-9]+).*/,"\\1","g"); }' < file

เอาต์พุตของไฟล์อินพุตตัวอย่างจะเป็น

12345

หมายเหตุ: gensub จะแทนที่ regex ทั้งหมด (ระหว่าง //) ดังนั้นคุณต้องใส่. * ก่อนและหลัง ([0-9] +) เพื่อกำจัดข้อความก่อนและหลังตัวเลขในการแทนที่


2
วิธีแก้ปัญหาที่ชาญฉลาดและใช้งานได้หากคุณต้องการ (หรือต้องการ) ใช้ gawk คุณสังเกตเห็นสิ่งนี้ แต่เพื่อความชัดเจน: awk ที่ไม่ใช่ GNU ไม่มี gensub () ดังนั้นจึงไม่รองรับสิ่งนี้
cincodenada

ดี! อย่างไรก็ตามควรใช้match()เพื่อเข้าถึงกลุ่มที่ถูกจับ ดูคำตอบของฉันสำหรับสิ่งนี้
fedorqui 'SO หยุดทำร้าย'

1

หากคุณต้องการเลือกเส้นให้ตัดส่วนที่คุณไม่ต้องการออก:

egrep 'abc[0-9]+xyz' inputFile | sed -e 's/^.*abc//' -e 's/xyz.*$//'

โดยทั่วไปจะเลือกบรรทัดที่คุณต้องการegrepจากนั้นใช้sedเพื่อตัดบิตก่อนและหลังตัวเลข

คุณสามารถดูการดำเนินการได้ที่นี่:

pax> echo 'a
b
c
abc12345xyz
a
b
c' | egrep 'abc[0-9]+xyz' | sed -e 's/^.*abc//' -e 's/xyz.*$//'
12345
pax> 

อัปเดต:เห็นได้ชัดว่าหากสถานการณ์จริงของคุณซับซ้อนมากขึ้น REs จะต้องแก้ไขให้ฉัน ตัวอย่างเช่นหากคุณมีตัวเลขตัวเดียวฝังอยู่ภายในศูนย์หรือมากกว่าที่ไม่ใช่ตัวเลขที่จุดเริ่มต้นและจุดสิ้นสุด:

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

น่าสนใจ ... ดังนั้นจึงไม่มีวิธีง่ายๆในการใช้นิพจน์ทั่วไปที่ซับซ้อนและกลับมาเฉพาะสิ่งที่อยู่ในส่วน (... )? เพราะในขณะที่ฉันเห็นสิ่งที่คุณทำที่นี่เป็นครั้งแรกด้วย grep แล้วด้วย sed สถานการณ์จริงของเราซับซ้อนกว่าการทิ้ง "abc" และ "xyz" มีการใช้นิพจน์ทั่วไปเนื่องจากข้อความที่แตกต่างกันจำนวนมากสามารถปรากฏที่ด้านใดด้านหนึ่งของข้อความที่ฉันต้องการแยกออก
Stéphane

ฉันแน่ใจว่าเป็นวิธีที่ดีกว่าถ้า REs มีความซับซ้อนจริงๆ บางทีหากคุณให้ตัวอย่างเพิ่มเติมหรือคำอธิบายที่ละเอียดกว่านี้เราสามารถปรับเปลี่ยนคำตอบให้เหมาะสมได้
paxdiablo

0

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

เนื่องจากความต้องการของ OP คือการแยกกลุ่มออกจากรูปแบบการใช้grep -oจะต้องใช้ 2 รอบ แต่ฉันยังคงพบว่าวิธีนี้เป็นวิธีที่ง่ายที่สุดในการทำงานให้สำเร็จ

$ cat > example.txt <<TXT
a
b
c
abc12345xyz
a
abc23451xyz asdf abc34512xyz
c
TXT

$ cat example.txt | grep -oE 'abc([0-9]+)xyz'
abc12345xyz
abc23451xyz
abc34512xyz

$ cat example.txt | grep -oE 'abc([0-9]+)xyz' | grep -oE '[0-9]+'
12345
23451
34512

เนื่องจากเวลาในการประมวลผลนั้นฟรี แต่ความสามารถในการอ่านของมนุษย์นั้นไม่มีค่าฉันจึงมักจะ refactor รหัสของฉันตามคำถามที่ว่า "หนึ่งปีต่อจากนี้ฉันจะคิดว่ามันจะเป็นอย่างไร" อันที่จริงสำหรับโค้ดที่ฉันตั้งใจจะแชร์แบบสาธารณะหรือกับทีมของฉันฉันจะเปิดman grepให้ดูว่าตัวเลือกแบบยาวคืออะไรและแทนที่โค้ดเหล่านั้น ชอบมาก:grep --only-matching --extended-regexp



-3

สำหรับ awk. ฉันจะใช้สคริปต์ต่อไปนี้:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }

สิ่งนี้ไม่ส่งออกค่าตัวเลข([0-9+])ซึ่งจะส่งออกทั้งบรรทัด
Mark Lakata

-3
gawk '/.*abc([0-9]+)xyz.*/' file

2
ดูเหมือนจะไม่ได้ผล มันพิมพ์ทั้งบรรทัดแทนการจับคู่
Stéphane

ในไฟล์อินพุตตัวอย่างของคุณรูปแบบนั้นคือทั้งบรรทัด ขวา??? ถ้าคุณรู้ว่ารูปแบบจะอยู่ในช่องเฉพาะ: ใช้ $ 1, $ 2 เป็นต้น .. เช่น gawk '$ 1 ~ /.*abc([0-9]+)xyz.*/' file
ghostdog74
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.