วิธีใช้ sed / grep เพื่อแยกข้อความระหว่างสองคำ?


134

ฉันกำลังพยายามส่งออกสตริงที่มีทุกอย่างระหว่างสองคำของสตริง:

การป้อนข้อมูล:

"Here is a String"

เอาท์พุท:

"is a"

โดยใช้:

sed -n '/Here/,/String/p'

มีจุดสิ้นสุด แต่ฉันไม่ต้องการรวมไว้


8
ผลลัพธ์ควรเป็นอย่างไรถ้าอินพุตเป็นHere is a Here String? หรือI Hereby Dub Thee Sir Stringy?
ghoti

5
FYI คำสั่งของคุณหมายถึงการพิมพ์ทุกอย่างระหว่างบรรทัดที่มีคำว่า Here และบรรทัดที่มีคำว่า String ไม่ใช่สิ่งที่คุณต้องการ
Hai Vu

sedคำถามที่พบบ่อยอื่น ๆคือ "ฉันจะแยกข้อความระหว่างบรรทัดเฉพาะได้อย่างไร"; นี่คือstackoverflow.com/questions/16643288/…
tripleee

คำตอบ:


109
sed -e 's/Here\(.*\)String/\1/'

2
ขอบคุณ! จะเกิดอะไรขึ้นถ้าฉันต้องการค้นหาทุกอย่างระหว่าง "one is" และ "String" ใน "Here is a String"? (sed -e 's / one คือ (. *) String / \ 1 /'?
user1190650

5
@ user1190650 จะได้ผลถ้าคุณต้องการดู "นี่คือ" เช่นกัน คุณสามารถทดสอบได้: echo "Here is a one is a String" | sed -e 's/one is\(.*\)String/\1/'. หากคุณเพียงแค่ต้องการเป็นส่วนหนึ่งระหว่าง "หนึ่ง" และ "สตริง" จากนั้นคุณจะต้องทำให้ regex sed -e 's/.*one is\(.*\)String.*/\1/'ตรงกับสายทั้ง: ใน sed ให้s/pattern/replacement/พูดว่า "แทนที่" แทน "สำหรับ" รูปแบบ "ในแต่ละบรรทัด" มันจะเปลี่ยนเฉพาะสิ่งที่ตรงกับ "รูปแบบ" เท่านั้นดังนั้นหากคุณต้องการให้มันแทนที่ทั้งเส้นคุณต้องทำให้ "รูปแบบ" ตรงกันทั้งบรรทัด
Brian Campbell

9
สิ่งนี้หยุดพักเมื่ออินพุตคือHere is a String Here is a String
Jay D

1
จะเป็นการดีที่จะเห็นวิธีแก้ปัญหาสำหรับกรณี: "นี่คือสตริง blah blah นี่คือ 1 a blah blah String นี่คือ 2 a blash blash String" เอาต์พุตควรเลือกเฉพาะสตริงย่อยแรกระหว่าง Here และ String "
Jay D

1
@JayD sed ไม่รองรับการจับคู่แบบไม่โลภดูคำถามนี้สำหรับทางเลือกที่แนะนำ
Brian Campbell

180

GNU grep ยังสามารถรองรับการมองไปข้างหน้าในเชิงบวกและเชิงลบสำหรับกรณีของคุณคำสั่งจะเป็น:

echo "Here is a string" | grep -o -P '(?<=Here).*(?=string)'

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

$ echo 'Here is a string, and Here is another string.' | grep -oP '(?<=Here).*(?=string)' # Greedy match
 is a string, and Here is another 
$ echo 'Here is a string, and Here is another string.' | grep -oP '(?<=Here).*?(?=string)' # Non-greedy match (Notice the '?' after '*' in .*)
 is a 
 is another 

31
โปรดทราบว่า-Pตัวเลือกของ GNU grep ไม่มีอยู่ในgrep* BSD หรือตัวเลือกที่มาพร้อมกับ SVR4 (Solaris ฯลฯ ) ใน FreeBSD คุณสามารถติดตั้งdevel/pcreพอร์ตซึ่งรวมถึงpcregrepซึ่งรองรับ PCRE (และมองไปข้างหน้า / ข้างหลัง) OSX เวอร์ชันเก่าใช้ GNU grep แต่ใน OSX Mavericks -Pนั้นมาจากเวอร์ชันของ FreeBSD ซึ่งไม่รวมตัวเลือก
ghoti

1
สวัสดีฉันจะแยกเนื้อหาที่แตกต่างออกไปเท่านั้นได้อย่างไร
Durgesh Suthar

4
นี้ไม่ได้ทำงานเพราะถ้าสิ้นสุดของสตริง "สตริง" เกิดขึ้นมากกว่าหนึ่งครั้งก็จะได้รับที่ผ่านมาเกิดขึ้นไม่ได้ต่อไปเกิดขึ้น
Buttle Butkus

6
ในกรณีของการHere is a string a string, ทั้งสอง " is a "และ" is a string a "เป็นคำตอบที่ถูกต้อง (ไม่สนใจคำพูด), ตามความต้องการของคำถาม ขึ้นอยู่กับคุณว่าคุณต้องการอันไหนจากนั้นคำตอบอาจแตกต่างกันไปตามนั้น อย่างไรก็ตามสำหรับความต้องการของคุณสิ่งนี้จะใช้ได้:echo "Here is a string a string" | grep -o -P '(?<=Here).*?(?=string)'
anishsane

2
@BND คุณต้องเปิดใช้งานคุณลักษณะการค้นหาหลายสายของ pcregrep echo $'Here is \na string' | grep -zoP '(?<=Here)(?s).*(?=string)'
anishsane

58

คำตอบที่ได้รับการยอมรับไม่ได้ลบข้อความที่อาจจะก่อนหรือหลังHere Stringนี่จะ:

sed -e 's/.*Here\(.*\)String.*/\1/'

ความแตกต่างที่สำคัญคือการเพิ่มขึ้นของ.*ทันทีก่อนและหลังHereString


คำตอบของคุณมีแนวโน้ม ประเด็นหนึ่งแม้ว่า ฉันจะแยกมันเป็น String ที่เห็นครั้งแรกได้อย่างไรหากมีหลาย String ในบรรทัดเดียวกัน? ขอบคุณ
Mian Asbat Ahmad

@MianAsbatAhmad คุณต้องการสร้างตัว*ระบุปริมาณระหว่างHereและStringไม่โลภ (หรือขี้เกียจ) แต่ประเภทของ regex ที่ใช้โดย sed ไม่รองรับปริมาณขี้เกียจ (ก?ทันทีหลังจาก.*) ตามนี้คำถาม Stackoverflow โดยปกติจะใช้ปริมาณขี้เกียจคุณก็จะตรงกับทุกอย่างยกเว้นโทเค็นที่คุณไม่ต้องการที่จะตรง Stringแต่ในกรณีนี้มีไม่ได้เป็นเพียงสัญลักษณ์เดียวแทนสตริงทั้งของตน
นายท้าย

ขอบคุณฉันได้คำตอบโดยใช้ awk, stackoverflow.com/questions/51041463/…
Mian Asbat Ahmad

น่าเสียดายที่สิ่งนี้ใช้ไม่ได้หากสตริงมีการแบ่งบรรทัด
Witalo Benicio

มันไม่ควร .ไม่ตรงกับการแบ่งบรรทัด ถ้าคุณต้องการเพื่อให้ตรงกับการแบ่งบรรทัดคุณสามารถแทนที่กับสิ่งที่ต้องการ. [\s\s]
ล้อ

35

คุณสามารถถอดสตริงในBashเพียงอย่างเดียว:

$ foo="Here is a String"
$ foo=${foo##*Here }
$ echo "$foo"
is a String
$ foo=${foo%% String*}
$ echo "$foo"
is a
$

และถ้าคุณมี GNU grep ที่มีPCREคุณสามารถใช้การยืนยันความกว้างเป็นศูนย์:

$ echo "Here is a String" | grep -Po '(?<=(Here )).*(?= String)'
is a

ทำไมวิธีนี้ถึงช้าจัง เมื่อลอกหน้า html ขนาดใหญ่โดยใช้วิธีนี้จะใช้เวลาประมาณ 10 วินาที
Adam Johns

@AdamJohns วิธีไหน PCRE หนึ่ง? PCRE ค่อนข้างซับซ้อนในการแยกวิเคราะห์ แต่ 10 วินาทีดูเหมือนจะรุนแรง หากคุณกังวลเราขอแนะนำให้คุณตั้งคำถามรวมถึงโค้ดตัวอย่างและดูว่าผู้เชี่ยวชาญพูดว่าอย่างไร
ghoti

ฉันคิดว่ามันช้ามากสำหรับฉันเพราะมันมีแหล่งที่มาของไฟล์ html ที่มีขนาดใหญ่มากในตัวแปร เมื่อฉันเขียนเนื้อหาลงในไฟล์และแยกวิเคราะห์ไฟล์ความเร็วจะเพิ่มขึ้นอย่างมาก
Adam Johns

22

ผ่าน GNU awk

$ echo "Here is a string" | awk -v FS="(Here|string)" '{print $2}'
 is a 

grep ที่มีพารามิเตอร์-P( perl-regexp ) รองรับ\Kซึ่งช่วยในการทิ้งอักขระที่จับคู่ก่อนหน้านี้ ในกรณีของเราสตริงที่ตรงกันก่อนหน้านี้Hereจึงถูกละทิ้งจากเอาต์พุตสุดท้าย

$ echo "Here is a string" | grep -oP 'Here\K.*(?=string)'
 is a 
$ echo "Here is a string" | grep -oP 'Here\K(?:(?!string).)*'
 is a 

หากคุณต้องการให้ผลลัพธ์เป็นis aคุณสามารถลองด้านล่างนี้

$ echo "Here is a string" | grep -oP 'Here\s*\K.*(?=\s+string)'
is a
$ echo "Here is a string" | grep -oP 'Here\s*\K(?:(?!\s+string).)*'
is a

สิ่งนี้ใช้ไม่ได้สำหรับ: echo "Here is a string dfdsf Here is a string" | awk -v FS="(Here|string)" '{print $2}'แต่จะกลับมาis aแทนที่จะเป็นis a is a@Avinash Raj
alper

20

หากคุณมีไฟล์ขนาดยาวที่มีจำนวนหลายบรรทัดการพิมพ์บรรทัดแรกจะมีประโยชน์:

cat -n file | sed -n '/Here/,/String/p'

3
ขอบคุณ! นี่เป็นวิธีแก้ปัญหาเดียวที่ใช้ได้ในกรณีของฉัน (ไฟล์ข้อความหลายบรรทัดแทนที่จะเป็นสตริงเดียวที่ไม่มีการแบ่งบรรทัด) เห็นได้ชัดว่าหากไม่มีการกำหนดหมายเลขบรรทัดต้องละเว้น-nตัวเลือกใน cat
Jeffrey Lebowski

... ซึ่งในกรณีนี้catสามารถละเว้นได้ทั้งหมด sedรู้วิธีอ่านไฟล์หรืออินพุตมาตรฐาน
tripleee

9

สิ่งนี้อาจได้ผลสำหรับคุณ (GNU sed):

sed '/Here/!d;s//&\n/;s/.*\n//;:a;/String/bb;$!{n;ba};:b;s//\n&/;P;D' file 

สิ่งนี้นำเสนอการแสดงข้อความแต่ละรายการระหว่างเครื่องหมายสองตัว (ในกรณีนี้HereและString) บนบรรทัดใหม่และรักษาบรรทัดใหม่ไว้ภายในข้อความ


7

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

    function str_str {
      local str
      str="${1#*${2}}"
      str="${str%%$3*}"
      echo -n "$str"
    }

    # test it ...
    mystr="this is a string"
    str_str "$mystr" "this " " string"

6

คุณสามารถใช้คำสั่งสองคำสั่ง

$ echo "Here is a String" | sed 's/.*Here//; s/String.*//'
 is a 

ยังใช้งานได้

$ echo "Here is a StringHere is a String" | sed 's/.*Here//; s/String.*//'
 is a

$ echo "Here is a StringHere is a StringHere is a StringHere is a String" | sed 's/.*Here//; s/String.*//'
 is a 

6

เพื่อให้เข้าใจsedคำสั่งเราต้องสร้างมันทีละขั้นตอน

นี่คือข้อความต้นฉบับของคุณ

user@linux:~$ echo "Here is a String"
Here is a String
user@linux:~$ 

ลองลบHereสตริงด้วยsตัวเลือก ubstition ในsed

user@linux:~$ echo "Here is a String" | sed 's/Here //'
is a String
user@linux:~$ 

ณ จุดนี้ฉันเชื่อว่าคุณจะสามารถลบออกได้Stringเช่นกัน

user@linux:~$ echo "Here is a String" | sed 's/String//'
Here is a
user@linux:~$ 

แต่นี่ไม่ใช่ผลลัพธ์ที่คุณต้องการ

ในการรวมคำสั่ง sed สองคำให้ใช้-eตัวเลือก

user@linux:~$ echo "Here is a String" | sed -e 's/Here //' -e 's/String//'
is a
user@linux:~$ 

หวังว่านี่จะช่วยได้


4

คุณสามารถใช้\1(อ้างถึงhttp://www.grymoire.com/Unix/Sed.html#uh-4 ):

echo "Hello is a String" | sed 's/Hello\(.*\)String/\1/g'

\1เนื้อหาที่อยู่ภายในวงเล็บจะถูกเก็บไว้เป็น


สิ่งนี้จะลบสตริงแทนที่จะส่งออกบางสิ่งที่อยู่ระหว่าง ลองลบ "Hello" ด้วย "is" ในคำสั่ง sed และจะแสดงผล "Hello a"
Jonathan

1

ปัญหา. ข้อความ Claws Mail ที่เก็บไว้ของฉันถูกรวมไว้ดังนี้และฉันกำลังพยายามแยกหัวเรื่อง:

Subject: [SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular
 link in major cell growth pathway: Findings point to new potential
 therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is
 Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as
 a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway
 identified [Lysosomal amino acid transporter SLC38A9 signals arginine
 sufficiency to mTORC1]]
Message-ID: <20171019190902.18741771@VictoriasJourney.com>

ต่อ A2 ในหัวข้อนี้วิธีใช้ sed / grep เพื่อแยกข้อความระหว่างสองคำ นิพจน์แรกด้านล่าง "ใช้ได้" ตราบใดที่ข้อความที่ตรงกันไม่มีขึ้นบรรทัดใหม่:

grep -o -P '(?<=Subject: ).*(?=molecular)' corpus/01

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key

อย่างไรก็ตามแม้จะลองใช้รูปแบบต่างๆมากมาย ( .+?; /s; ...) แต่ฉันก็ไม่สามารถใช้งานได้:

grep -o -P '(?<=Subject: ).*(?=link)' corpus/01
grep -o -P '(?<=Subject: ).*(?=therapeutic)' corpus/01
etc.

โซลูชันที่ 1.

ต่อแยกข้อความระหว่างสองสตริงในบรรทัดที่แตกต่างกัน

sed -n '/Subject: /{:a;N;/Message-ID:/!ba; s/\n/ /g; s/\s\s*/ /g; s/.*Subject: \|Message-ID:.*//g;p}' corpus/01

ซึ่งจะช่วยให้

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular link in major cell growth pathway: Findings point to new potential therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway identified [Lysosomal amino acid transporter SLC38A9 signals arginine sufficiency to mTORC1]]                              

โซลูชันที่ 2 *

ต่อฉันจะเปลี่ยนบรรทัดใหม่ (\ n) โดยใช้ sed ได้อย่างไร

sed ':a;N;$!ba;s/\n/ /g' corpus/01

จะแทนที่บรรทัดใหม่ด้วยช่องว่าง

การเชื่อมโยงกับ A2 ในวิธีใช้ sed / grep เพื่อแยกข้อความระหว่างสองคำ? , เราได้รับ:

sed ':a;N;$!ba;s/\n/ /g' corpus/01 | grep -o -P '(?<=Subject: ).*(?=Message-ID:)'

ซึ่งจะช่วยให้

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular  link in major cell growth pathway: Findings point to new potential  therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is  Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as  a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway  identified [Lysosomal amino acid transporter SLC38A9 signals arginine  sufficiency to mTORC1]] 

ตัวแปรนี้ลบช่องว่างคู่:

sed ':a;N;$!ba;s/\n/ /g; s/\s\s*/ /g' corpus/01 | grep -o -P '(?<=Subject: ).*(?=Message-ID:)'

ให้

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular link in major cell growth pathway: Findings point to new potential therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway identified [Lysosomal amino acid transporter SLC38A9 signals arginine sufficiency to mTORC1]]

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