จะส่งออกเฉพาะกลุ่มที่ถูกจับได้อย่างไร


277

มีวิธีบอกsedให้ส่งออกเฉพาะกลุ่มที่จับหรือไม่ ตัวอย่างเช่นกำหนดอินพุต:

This is a sample 123 text and some 987 numbers

และรูปแบบ:

/([\d]+)/

ฉันจะได้รับเพียง 123 และ 987 เอาท์พุทในวิธีการจัดรูปแบบโดยอ้างอิงกลับ?


หมายเหตุการจับภาพกลุ่มต้องsedเปิดใช้งานนิพจน์ทั่วไปแบบขยายด้วยการ-Eตั้งค่าสถานะ
peterh - Reinstate Monica

คำตอบ:


333

กุญแจสำคัญในการทำให้สิ่งนี้ทำงานได้คือการบอกsedให้แยกสิ่งที่คุณไม่ต้องการออกรวมทั้งระบุสิ่งที่คุณต้องการ

string='This is a sample 123 text and some 987 numbers'
echo "$string" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

สิ่งนี้พูดว่า:

  • อย่าเริ่มต้นที่จะพิมพ์แต่ละบรรทัด ( -n)
  • ยกเว้นศูนย์ที่ไม่ใช่ตัวเลขหรือมากกว่า
  • รวมหนึ่งหลักหรือมากกว่า
  • ยกเว้นตัวเลขที่ไม่ใช่ตัวเลขอย่างน้อยหนึ่งหลัก
  • รวมหนึ่งหลักหรือมากกว่า
  • ยกเว้นศูนย์ที่ไม่ใช่ตัวเลขหรือมากกว่า
  • พิมพ์การทดแทน ( p)

โดยทั่วไปแล้วsedคุณจับภาพกลุ่มโดยใช้วงเล็บและส่งออกสิ่งที่คุณถ่ายโดยใช้การอ้างอิงย้อนกลับ:

echo "foobarbaz" | sed 's/^foo\(.*\)baz$/\1/'

จะเอาท์พุท "บาร์" หากคุณใช้-r( -Eสำหรับ OS X) สำหรับการขยายเพิ่มเติมคุณไม่จำเป็นต้องหลีกเลี่ยงวงเล็บ:

echo "foobarbaz" | sed -r 's/^foo(.*)baz$/\1/'

สามารถมีกลุ่มการจับได้สูงสุด 9 กลุ่มและการอ้างอิงกลับ การอ้างอิงด้านหลังมีหมายเลขตามลำดับที่กลุ่มปรากฏ แต่สามารถใช้ในลำดับใดก็ได้และสามารถทำซ้ำได้:

echo "foobarbaz" | sed -r 's/^foo(.*)b(.)z$/\2 \1 \2/'

เอาต์พุต "a bar a"

หากคุณมี GNU grep(มันอาจทำงานใน BSD รวมถึง OS X):

echo "$string" | grep -Po '\d+'

หรือรูปแบบต่างๆเช่น:

echo "$string" | grep -Po '(?<=\D )(\d+)'

-Pตัวเลือกที่ช่วยให้การแสดงผลปกติ Perl เข้ากันได้ ดูหรือman 3 pcrepatternman 3 pcresyntax


24
ตามหมายเหตุ OSX Mountain Lion ไม่สนับสนุน PCRE ใน grep อีกต่อไป
yincrash

1
ตามหมายเหตุด้านข้างตัวเลือก grep -o ไม่รองรับบน Solaris 9 นอกจากนี้ Solaris 9 ไม่รองรับตัวเลือก sed -r :(
Daniel Kats

7
ขอดูแลระบบของคุณเพื่อติดตั้ง gsed คุณจะประหลาดใจกับสิ่งที่โดนัทเพียงไม่กี่ตัวที่จะพาคุณไป ...
avgvstvs

3
โปรดทราบว่าคุณอาจต้องนำหน้า '(' และ ')' ด้วย '\' ฉันไม่ทราบสาเหตุ
lumbric

7
@lumbric: หากคุณอ้างถึงsedตัวอย่างหากคุณใช้-rตัวเลือก (หรือ-Eสำหรับ OS X, IIRC) คุณไม่จำเป็นต้องหลีกเลี่ยงวงเล็บ ความแตกต่างคือระหว่างนิพจน์ทั่วไปพื้นฐานและนิพจน์ทั่วไปที่ขยายเพิ่ม ( -r)
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

55

Sed มีรูปแบบที่จำได้มากถึงเก้ารูปแบบ แต่คุณต้องใช้วงเล็บที่ใช้ Escape เพื่อจดจำส่วนต่าง ๆ ของนิพจน์ทั่วไป

ดูที่นี่สำหรับตัวอย่างและรายละเอียดเพิ่มเติม


58
sed -e 's/version=\(.+\)/\1/' input.txtนี้จะยังคงส่งออก input.txt ทั้งหมด
Pablo

@Pablo, ในรูปแบบของคุณคุณต้องเขียนแทน\+ +และฉันก็ไม่เข้าใจว่าทำไมผู้คนถึงใช้-eเพียงคำสั่งเดียว
Fredrick Gauss

1
ใช้sed -e -n 's/version=\(.+\)/\1/p' input.txtดู: mikeplate.com/2012/05/09/…
awattar

1
ฉันขอแนะนำให้ใช้sed -Eเพื่อใช้นิพจน์ทั่วไป "ทันสมัย" หรือ "ขยาย" ที่มีลักษณะใกล้เคียงกับ Perl / Java / JavaScript / Go / รสชาติใด ๆ (เปรียบเทียบกับgrep -Eหรือegrep.) ไวยากรณ์เริ่มต้นมีกฎการหลบหนีที่แปลกและถือว่า "ล้าสมัย" man 7 re_formatสำหรับข้อมูลเพิ่มเติมเกี่ยวกับความแตกต่างระหว่างทั้งสองวิ่ง
AndrewF

31

คุณสามารถใช้ grep

grep -Eow "[0-9]+" file

4
@ ghostdog74: เห็นด้วยอย่างยิ่งกับคุณ ฉันจะได้รับ greo เพื่อส่งออกเฉพาะกลุ่มที่จับได้อย่างไร
Pablo

1
@Michael - นั่นคือเหตุผลที่มีoตัวเลือก - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, - เฉพาะการจับคู่แสดงเฉพาะส่วนของเส้นที่ตรงกันที่ตรงกับรูปแบบ
Bert F

14
@Bert F: ฉันเข้าใจส่วนที่ตรงกัน แต่ไม่ได้จับภาพกลุ่ม สิ่งที่ฉันต้องการคือมีสิ่งนี้ ([0-9] +). + ([abc] {2,3}) ดังนั้นจึงมีกลุ่มจับภาพ 2 กลุ่ม ฉันต้องการส่งออกเฉพาะการจับภาพกลุ่มโดยการอ้างอิงกลับหรืออย่างอื่น
Pablo

สวัสดีไมเคิล คุณจัดการแยกกลุ่มที่ถูกดักจับด้วย grep ได้ไหม?
doc_id

1
@Pablo: grep ส่งผลลัพธ์เฉพาะสิ่งที่ตรงกัน หากต้องการให้หลายกลุ่มใช้หลายนิพจน์: grep -Eow -e "[0-9]+" -e "[abc]{2,3}"ฉันไม่ทราบว่าคุณต้องการให้สองนิพจน์เหล่านี้อยู่ในหนึ่งบรรทัดนอกเหนือจาก piping จาก grep ก่อนหน้านี้ได้อย่างไร (ซึ่งยังคงใช้งานไม่ได้หากรูปแบบตรงกันมากกว่าหนึ่งครั้ง )
idbrii

13

จำนวนหลัก

คำตอบนี้ใช้ได้กับทุกกลุ่มหลัก ตัวอย่าง:

$ echo 'Num123that456are7899900contained0018166intext' |
> sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

คำตอบที่ขยาย

มีวิธีใดบ้างที่จะบอกให้ส่งออกเฉพาะกลุ่มที่ถูกจับได้?

ใช่. แทนที่ข้อความทั้งหมดโดยกลุ่มการดักจับ:

$ echo 'Number 123 inside text' | sed 's/[^0-9]*\([0-9]\{1,\}\)[^0-9]*/\1/'
123

s/[^0-9]*                           # several non-digits
         \([0-9]\{1,\}\)            # followed by one or more digits
                        [^0-9]*     # and followed by more non-digits.
                               /\1/ # gets replaced only by the digits.

หรือด้วยไวยากรณ์เพิ่มเติม (backquotes น้อยและอนุญาตให้ใช้ +):

$ echo 'Number 123 in text' | sed -E 's/[^0-9]*([0-9]+)[^0-9]*/\1/'
123

เพื่อหลีกเลี่ยงการพิมพ์ข้อความต้นฉบับเมื่อไม่มีหมายเลขให้ใช้:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) อย่าพิมพ์อินพุตตามค่าเริ่มต้น
  • (/ p) พิมพ์เฉพาะเมื่อมีการเปลี่ยนใหม่

และเพื่อให้ตรงกับตัวเลขหลายตัว (และพิมพ์ด้วย):

$ echo 'N 123 in 456 text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1 /gp'
123 456

ใช้งานได้กับการนับจำนวนหลัก ๆ :

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

ซึ่งคล้ายกับคำสั่ง grep มาก:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | grep -Po '\d+'
123
456
7899900
0018166

เกี่ยวกับ \ d

และรูปแบบ: /([\d]+)/

Sed ไม่รู้จักไวยากรณ์ '\ d' (ทางลัด) การเทียบ ASCII ที่ใช้ด้านบน[0-9]นั้นไม่เทียบเท่ากันทั้งหมด ทางเลือกเดียวคือการใช้คลาสอักขระ: '[[: หลัก:]]'

คำตอบที่เลือกใช้ "คลาสตัวละคร" เพื่อสร้างโซลูชัน:

$ str='This is a sample 123 text and some 987 numbers'
$ echo "$str" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

โซลูชันนั้นใช้งานได้สำหรับ (สอง) ตัวเลขสองหลักเท่านั้น

แน่นอนว่าเมื่อคำตอบนั้นดำเนินการภายในเชลล์เราสามารถกำหนดตัวแปรสองตัวเพื่อทำให้คำตอบสั้นลง:

$ str='This is a sample 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D+($d+)$D*/\1 \2/p"

แต่อย่างที่อธิบายไว้แล้วการใช้s/…/…/gpคำสั่งดีกว่า:

$ str='This is 75577 a sam33ple 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D*/\1 /gp"
75577 33 123 987

ซึ่งจะครอบคลุมทั้งตัวเลขซ้ำ ๆ และเขียนคำสั่งสั้น ๆ (er)


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

9

ฉันเชื่อว่ารูปแบบที่ให้ไว้ในคำถามเป็นเพียงตัวอย่างเท่านั้นและเป้าหมายคือเพื่อให้ตรงกับรูปแบบใด ๆ

หากคุณมีความคิดสร้างสรรค์กับส่วนขยาย GNU ที่อนุญาตให้แทรกบรรทัดใหม่ในพื้นที่รูปแบบหนึ่งข้อเสนอแนะคือ:

> set string = "This is a sample 123 text and some 987 numbers"
>
> set pattern = "[0-9][0-9]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
123
987
> set pattern = "[a-z][a-z]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
his
is
a
sample
text
and
some
numbers

ตัวอย่างเหล่านี้มาพร้อมกับ tcsh (ใช่ฉันรู้ว่ามันผิด) กับ CYGWIN (แก้ไข: สำหรับทุบตีลบชุดและช่องว่างรอบ ๆ =.)


@ โจเซฟ: ขอบคุณอย่างไรก็ตามจากงานของฉันฉันรู้สึกว่า grep เป็นธรรมชาติมากขึ้นเช่น ghostdog74 แนะนำ เพียงแค่ต้องหาวิธีที่จะทำให้ grep เอาต์พุตกลุ่มการดักจับเท่านั้นไม่ใช่การจับคู่ทั้งหมด
Pablo

2
เพียงแค่ทราบ แต่เครื่องหมายบวก '+' หมายถึง 'หนึ่งหรือมากกว่า' ซึ่งจะลบความต้องการสำหรับการทำซ้ำตัวเองในรูปแบบ ดังนั้น "[0-9] [0-9] *" จะกลายเป็น "[0-9] +"
RandomInsano

4
@RandomInsano: ในการใช้งาน+คุณจะต้องหลบหนีหรือใช้-rตัวเลือก ( -Eสำหรับ OS X) นอกจากนี้คุณยังสามารถใช้\{1,\}(หรือ-rหรือ-Eไม่มีการหลบหนี)
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

9

ยอมแพ้และใช้ Perl

เนื่องจากsedไม่ได้ตัดเราลองโยนผ้าเช็ดตัวและใช้ Perl อย่างน้อยก็เป็นLSBในขณะที่grepส่วนขยายของ GNU ไม่ใช่ :-)

  • พิมพ์ส่วนที่ตรงกันทั้งหมดโดยไม่จำเป็นต้องมีกลุ่มการจับคู่หรือการค้นหา:

    cat <<EOS | perl -lane 'print m/\d+/g'
    a1 b2
    a34 b56
    EOS

    เอาท์พุท:

    12
    3456
  • การจับคู่เดี่ยวต่อบรรทัดฟิลด์ข้อมูลที่มีโครงสร้างมักจะ:

    cat <<EOS | perl -lape 's/.*?a(\d+).*/$1/g'
    a1 b2
    a34 b56
    EOS

    เอาท์พุท:

    1
    34

    ด้วย lookbehind:

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • หลายช่อง:

    cat <<EOS | perl -lape 's/.*?a(\d+).*?b(\d+).*/$1 $2/g'
    a1 c0 b2 c0
    a34 c0 b56 c0
    EOS

    เอาท์พุท:

    1 2
    34 56
  • การจับคู่หลายรายการต่อบรรทัดข้อมูลที่ไม่มีโครงสร้างมักจะ:

    cat <<EOS | perl -lape 's/.*?a(\d+)|.*/$1 /g'
    a1 b2
    a34 b56 a78 b90
    EOS

    เอาท์พุท:

    1 
    34 78

    ด้วย lookbehind:

    cat EOS<< | perl -lane 'print m/(?<=a)(\d+)/g'
    a1 b2
    a34 b56 a78 b90
    EOS

    เอาท์พุท:

    1
    3478

1
คุณไม่ได้อะไรในตอนท้ายของคำถาม: "with sed"?
Moonchild

@ Moonchild ชาว Google ไม่สนใจ
Ciro Santilli 法轮功冠状病六四事件法轮功

1
ฉันพบว่ามีประโยชน์นี้ ไม่ใช่ปัญหาบรรทัดคำสั่ง regex ทั้งหมดที่ต้องแก้ไขด้วย sed
PPPaul

5

ลอง

sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

ฉันได้รับสิ่งนี้ภายใต้ cygwin:

$ (echo "asdf"; \
   echo "1234"; \
   echo "asdf1234adsf1234asdf"; \
   echo "1m2m3m4m5m6m7m8m9m0m1m2m3m4m5m6m7m8m9") | \
  sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

1234
1234 1234
1 2 3 4 5 6 7 8 9
$

2

มันไม่ใช่สิ่งที่ OP ขอ (จับภาพกลุ่ม) แต่คุณสามารถแยกตัวเลขโดยใช้:

S='This is a sample 123 text and some 987 numbers'
echo "$S" | sed 's/ /\n/g' | sed -r '/([0-9]+)/ !d'

ให้สิ่งต่อไปนี้:

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