การจับคู่ regex ไม่โลภ (ลังเล) ใน sed หรือไม่


406

ฉันพยายามใช้ sed เพื่อล้างบรรทัด URL เพื่อแยกโดเมนออก

ดังนั้นจาก:

http://www.suepearson.co.uk/product/174/71/3816/

ฉันต้องการ:

http://www.suepearson.co.uk/

(ไม่ว่าจะมีหรือไม่มีเครื่องหมายทับท้ายก็ไม่สำคัญ)

ฉันเหนื่อย:

 sed 's|\(http:\/\/.*?\/\).*|\1|'

และ (หนีออกมาจากปริมาณที่ไม่โลภ)

sed 's|\(http:\/\/.*\?\/\).*|\1|'

แต่ดูเหมือนว่าฉันจะไม่สามารถให้ quantifier ที่ไม่โลภ ( ?) ทำงานดังนั้นมันจะจับคู่สตริงทั้งหมดเสมอ


54
หมายเหตุด้านข้าง: หากคุณกำหนด regexes ด้วย "|" คุณไม่จำเป็นต้องหลบหนี "/" s ในความเป็นจริงคนส่วนใหญ่คั่นด้วย "|" แทน "/" s เพื่อหลีกเลี่ยง "รั้วรั้ว"
AttishOculus

12
@AttishOculus ตัวละครตัวแรกหลังจาก 's' ในการแสดงออกแทนใน sed เป็นตัวคั่น ดังนั้น ^ foo ^ bar ^ 'หรือ' s! foo! bar! ' ยังใช้งานได้
Squidly

1
สำหรับ regex sed -E 's...ขยายการใช้งาน ยังไม่มีผู้ประกอบการลังเล
Ondra Žižka

ไม่ใช่คำตอบสำหรับชื่อคำถาม แต่ในกรณีนี้สามารถใช้cut -d'/' -f1-3งานได้ง่าย
Petr Javorik

คำตอบ:


421

ทั้งพื้นฐานและขยาย Posix / GNU regex ตระหนักถึงปริมาณที่ไม่โลภ; คุณต้องมี regex ในภายหลัง โชคดีที่ Perl regex สำหรับบริบทนี้ค่อนข้างง่ายที่จะได้รับ:

perl -pe 's|(http://.*?/).*|\1|'

12
-pi -eสำหรับการทำไว้ในสถานที่ใช้ตัวเลือก
จริงๆ

11
ศักดิ์สิทธิ์สูบบุหรี่ฉันไม่สามารถเชื่อว่าทำงาน :-) สิ่งเดียวที่ sucks คือตอนนี้สคริปต์ของฉันมีการพึ่งพา Perl :-( ในด้านบวกแทบทุก distro Linux มี Perl แล้วดังนั้นอาจจะไม่เป็นปัญหา :-)
Freedom_Ben

6
@Freedom_Ben: IIRC perlถูกต้องโดย POSIX
MestreLion

4
@ dolphus333: "ทั้งแบบพื้นฐานและแบบขยาย Posix / GNU regex ไม่รู้จักการหาปริมาณที่ไม่โลภ" หมายความว่า "คุณไม่สามารถใช้ตัวนับที่ไม่ใช่ความโลภใน sed"
ความโกลาหล

3
@ Sérgioมันเป็นวิธีที่คุณทำสิ่งที่ร้องขอซึ่งเป็นไปไม่ได้ในsedการใช้ไวยากรณ์โดยทั่วไปเหมือนกับว่าsed
ความวุ่นวาย

250

ในกรณีเฉพาะนี้คุณสามารถทำงานให้เสร็จได้โดยไม่ต้องใช้ regex ที่ไม่โลภ

ลองใช้ regex ที่ไม่ใช่โลภ[^/]*แทน.*?:

sed 's|\(http://[^/]*/\).*|\1|g'

3
วิธีที่จะทำให้การจับคู่วลีที่ไม่โลภโดยใช้เทคนิคนี้ได้อย่างไร?
user3694243

6
น่าเสียดายที่คุณทำไม่ได้ ดูคำตอบของความสับสนวุ่นวาย
Daniel H

ขอบคุณมาก ... เนื่องจาก Perl ไม่ได้อยู่ในฐานการติดตั้งเริ่มต้นใน distros linux อีกต่อไป!
st0ne


@DanielH ในความเป็นจริงเป็นไปได้ที่จะจับคู่วลีที่ไม่โลภโดยใช้เทคนิคนี้ตามที่ร้องขอ อาจต้องใช้ความเจ็บปวดบ้างในการเขียนลวดลายทั้งสองด้วยความแม่นยำที่เพียงพอ เช่นเมื่อแยกคีย์มูลค่าที่ได้รับมอบหมายในการค้นหาของ URL ของมันอาจจะต้องมอบหมาย seearch ([^&=#]+)=([^&#]*)ใช้ มีบางกรณีที่ไม่ได้ทำงานในลักษณะนี้อย่างแน่นอนเช่นเมื่อแยก URL สำหรับส่วนโฮสต์และชื่อพา ธ ที่มีเครื่องหมายทับสุดท้ายสันนิษฐานว่าเป็นทางเลือกที่จะไม่รวมอยู่ในการจับภาพ:^(http:\/\/.+?)/?$
Thomas Urban

121

ด้วย sed ฉันมักจะใช้การค้นหาที่ไม่โลภโดยการค้นหาอะไรก็ได้ยกเว้นตัวคั่นจนถึงตัวคั่น:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'

เอาท์พุท:

http://www.suon.co.uk

นี่คือ:

  • อย่าเอาท์พุท -n
  • ค้นหา, จับคู่รูปแบบ, แทนที่และพิมพ์ s/<pattern>/<replace>/p
  • ใช้;ตัวคั่นคำสั่งค้นหาแทน/เพื่อให้พิมพ์ได้ง่ายขึ้นs;<pattern>;<replace>;p
  • จำไว้ว่าการแข่งขันระหว่างวงเล็บ\(... \)หลังจากนั้นสามารถเข้าถึงได้ด้วย\1, \2...
  • การจับคู่ http://
  • ตามมาด้วยอะไรในวงเล็บ[], [ab/]จะหมายถึงการอย่างใดอย่างหนึ่งaหรือbหรือ/
  • อันดับแรก^ในความ[]หมายnotดังนั้นตามด้วยสิ่งใดนอกจากสิ่งใน[]
  • ดังนั้น[^/]หมายความว่าอะไรยกเว้น/ตัวละคร
  • *คือการทำซ้ำกลุ่มก่อนหน้านี้เพื่อให้ตัวละครวิธีการยกเว้น[^/]*/
  • จนถึงตอนนี้sed -n 's;\(http://[^/]*\)หมายถึงการค้นหาและจดจำhttp://ตามด้วยอักขระใด ๆ ยกเว้น/และจำสิ่งที่คุณพบ
  • เราต้องการค้นหาจนถึงจุดสิ้นสุดของโดเมนดังนั้นหยุดในวันถัดไป/เพื่อเพิ่มอีก/อันตอนท้าย: sed -n 's;\(http://[^/]*\)/'แต่เราต้องการจับคู่ส่วนที่เหลือของบรรทัดหลังโดเมนเพื่อเพิ่ม.*
  • ตอนนี้การแข่งขันที่จำได้ในกลุ่ม 1 ( \1) เป็นโดเมนดังนั้นแทนที่สายที่จับคู่ด้วยสิ่งที่บันทึกไว้ในกลุ่ม\1และพิมพ์:sed -n 's;\(http://[^/]*\)/.*;\1;p'

หากคุณต้องการรวมแบ็กสแลชหลังโดเมนด้วยให้เพิ่มแบ็กสแลชอีกหนึ่งรายการในกลุ่มเพื่อให้จำ:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p'

เอาท์พุท:

http://www.suon.co.uk/

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

2
เป็นไปได้ที่จะแทนที่ตัวคั่นด้วยสตริงหรือไม่?
Calculemus

37

sed ไม่รองรับผู้ประกอบการ "ไม่โลภ"

คุณต้องใช้โอเปอเรเตอร์ "[]" เพื่อยกเว้น "/" จากการจับคู่

sed 's,\(http://[^/]*\)/.*,\1,'

PS ไม่จำเป็นต้องแบ็กสแลช "/"


ไม่ได้จริงๆ หากตัวคั่นอาจเป็นหนึ่งในตัวละครที่เป็นไปได้มากมาย (พูดเป็นตัวเลขเท่านั้น) การจับคู่การปฏิเสธของคุณอาจมีความซับซ้อนมากขึ้น นั่นเป็นเรื่องปกติ แต่มันก็ดีที่จะมีตัวเลือกในการทำ * ไม่ใช่โลภ
gesell

1
คำถามทั่วไปมากขึ้น โซลูชันเหล่านี้ใช้ได้กับ URL แต่ไม่ (เช่น) สำหรับกรณีการใช้งานของฉันในการเปิดศูนย์ต่อท้าย เห็นได้ชัดว่าจะไม่ทำงานได้ดีสำหรับs/([[:digit:]]\.[[1-9]]*)0*/\1/ 1.20300อย่างไรก็ตามเนื่องจากคำถามดั้งเดิมเกี่ยวกับ URL จึงควรกล่าวถึงในคำตอบที่ยอมรับได้
Daniel H

33

การจำลอง quantifier ขี้เกียจ (ไม่โลภ) ใน sed

และรสชาติอื่น ๆ ของ regex!

  1. การค้นหานิพจน์แรกที่เกิดขึ้น:

    • POSIX ERE (ใช้-rตัวเลือก)

      regex:

      (EXPRESSION).*|.

      sed:

      sed -r 's/(EXPRESSION).*|./\1/g' # Global `g` modifier should be on

      ตัวอย่าง (ค้นหาลำดับแรกของตัวเลข) การสาธิตสด :

      $ sed -r 's/([0-9]+).*|./\1/g' <<< 'foo 12 bar 34'
      12

      มันทำงานยังไง?

      นี้ผลประโยชน์ regex |จากการสลับ ในแต่ละตำแหน่งเอ็นจินพยายามที่จะเลือกคู่ที่ยาวที่สุด (นี่คือมาตรฐาน POSIX ซึ่งตามมาด้วยคู่ของเอ็นจินอื่น ๆ ด้วย) ซึ่งหมายความว่า.จนกว่าจะพบการ([0-9]+).*แข่งขัน แต่คำสั่งซื้อก็มีความสำคัญเช่นกัน

      ป้อนคำอธิบายรูปภาพที่นี่

      เนื่องจากมีการตั้งค่าสถานะโกลบอลเอ็นจินจะพยายามดำเนินการจับคู่อักขระตามอักขระจนถึงจุดสิ้นสุดของสตริงอินพุตหรือเป้าหมายของเรา ทันทีที่แรกและที่เดียวในกลุ่มของการจับด้านซ้ายสลับกันเป็นคู่เหลือของสายการบริโภคทันทีเช่นกัน(EXPRESSION) .*ตอนนี้เราถือคุณค่าของเราในกลุ่มการจับภาพแรก

    • POSIX BRE

      regex:

      \(\(\(EXPRESSION\).*\)*.\)*

      sed:

      sed 's/\(\(\(EXPRESSION\).*\)*.\)*/\3/'

      ตัวอย่าง (การหาลำดับแรกของตัวเลข):

      $ sed 's/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/' <<< 'foo 12 bar 34'
      12

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

      ป้อนคำอธิบายรูปภาพที่นี่

      หากพบตัวเลขอื่น ๆ ต่อไปนี้จะถูกใช้และจับภาพและส่วนที่เหลือของบรรทัดจะถูกจับคู่ทันทีมิฉะนั้นจะ*หมายความว่า มากกว่าหรือเป็นศูนย์จะข้ามกลุ่มการจับภาพที่สอง\(\([0-9]\{1,\}\).*\)*และมาถึงจุด.เพื่อจับคู่อักขระเดี่ยวและกระบวนการนี้ดำเนินต่อไป

  2. การค้นหาสิ่งที่เกิดขึ้นครั้งแรกของ แสดงออกที่คั่นด้วย :

    วิธีการนี้จะตรงกับการเกิดขึ้นครั้งแรกของสตริงที่คั่นด้วย เราสามารถเรียกมันว่าบล็อกของสตริง

    sed 's/\(END-DELIMITER-EXPRESSION\).*/\1/; \
         s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g'

    สตริงอินพุต:

    foobar start block #1 end barfoo start block #2 end

    -EDE: end

    -SDE: start

    $ sed 's/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g'

    เอาท์พุท:

    start block #1 end

    การ\(end\).*จับคู่regex แรกและการจับภาพตัวคั่นสุดท้ายendและทดแทนทั้งหมดจับคู่กับอักขระที่จับล่าสุดซึ่งเป็นตัวคั่นท้าย ในขั้นตอนนี้ผลลัพธ์ของเราคือ: foobar start block #1 end.

    ป้อนคำอธิบายรูปภาพที่นี่

    จากนั้นผลลัพธ์จะถูกส่งไปยัง regex ที่สอง\(\(start.*\)*.\)*ที่เหมือนกับ POSIX BRE เวอร์ชันด้านบน มันตรงกับอักขระตัวเดียวหากตัวคั่นเริ่มต้นstartไม่ตรงกันมิฉะนั้นจะจับคู่และจับตัวคั่นเริ่มต้นและจับคู่อักขระที่เหลือ

    ป้อนคำอธิบายรูปภาพที่นี่


ตอบคำถามของคุณโดยตรง

การใช้วิธี # 2 (นิพจน์ที่คั่นด้วย) คุณควรเลือกสองนิพจน์ที่เหมาะสม:

  • EDE: [^:/]\/

  • SDE: http:

การใช้งาน:

$ sed 's/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/' <<< 'http://www.suepearson.co.uk/product/174/71/3816/'

เอาท์พุท:

http://www.suepearson.co.uk/

หมายเหตุ: สิ่งนี้จะไม่ทำงานกับตัวคั่นที่เหมือนกัน


3) ในขณะที่แนะนำไซต์เช่น regex101 สำหรับการสาธิตโปรดเพิ่มหมายเหตุว่ามันไม่เหมาะสำหรับเครื่องมือ cli เนื่องจากไวยากรณ์และความแตกต่างของคุณสมบัติ
Sundeep

1
@Sundeep ขอบคุณ ฉันเปลี่ยนคำพูดทั้งหมดเป็นคำพูดเดี่ยว ฉันยังถือว่ากฎการแข่งขันที่ยาวที่สุดซ้ายสุดที่จะกล่าวถึง อย่างไรก็ตามในsedและเครื่องยนต์อื่น ๆ ทั้งหมดที่ทำตามคำสั่งมาตรฐานเดียวกันนั้นไม่สำคัญเมื่อมันมาถึงความเท่าเทียมกัน ดังนั้นecho 'foo 1' | sed -r 's/.|([0-9]+).*/\1/g'ไม่มีการแข่งขัน แต่echo 'foo 1' | sed -r 's/([0-9]+).*|./\1/g'ทำ
revo

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

จุดที่ดีเกี่ยวกับสิ่งที่เกิดขึ้นเมื่อ alternations ที่แตกต่างกันเริ่มต้นจากสถานที่เดียวกันและมีความยาวเดียวกันเดาว่าจะปฏิบัติตามคำสั่งซ้ายขวาเหมือนเครื่องมืออื่น ๆ .. จำเป็นที่จะต้องขึ้นไปดูว่าที่อธิบายไว้ในคู่มือ
Sundeep

มีกรณีแปลก ๆ อยู่ที่นี่: stackoverflow.com/questions/59683820/…
Sundeep

20

วิธีการแก้ปัญหาไม่โลภสำหรับตัวละครมากกว่าหนึ่งตัว

หัวข้อนี้เก่ามาก แต่ฉันคิดว่าผู้คนยังต้องการมันอยู่ HELLOช่วยบอกว่าคุณต้องการที่จะฆ่าทุกอย่างจนเกิดขึ้นครั้งแรกมาก คุณไม่สามารถพูดได้[^HELLO]...

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

ในกรณีนี้เราสามารถ:

s/HELLO/top_sekrit/     #will only replace the very first occurrence
s/.*top_sekrit//        #kill everything till end of the first HELLO

แน่นอนด้วยการป้อนข้อมูลที่ง่ายกว่าคุณสามารถใช้คำที่มีขนาดเล็กลงหรืออาจเป็นอักขระเดียวก็ได้

HTH!


4
เพื่อให้ดียิ่งขึ้นมีประโยชน์ในสถานการณ์เมื่อคุณไม่สามารถคาดหวังอักขระที่ไม่ได้ใช้: 1. แทนที่อักขระพิเศษด้วย WORD ที่ไม่ได้ใช้จริง ๆ 2. แทนที่ลำดับลงท้ายด้วยอักขระพิเศษ 3. ทำการค้นหาด้วยอักขระพิเศษ 4 . แทนที่อักขระพิเศษด้านหลัง 5. แทนที่ WORD พิเศษด้านหลัง ตัวอย่างเช่นคุณต้องการโอเปอเรเตอร์โลภระหว่าง <hello> ถึง </hello>:
Jakub

3
ตัวอย่างต่อไปนี้: echo "Find: <hello> fir ~ st <br> yes </hello> <hello> sec ~ ond </hello>" | sed -e "s, ~, VERYSPECIAL, g" -e "s, </hello>, ~, g" -e "s,. * ค้นหา: <hello> ([^ ~] *). *, \ 1 , "-e" s, \ ~, </hello>, "-e" s, VERYSPECIAL, ~, "
Jakub

2
ฉันเห็นด้วย. ทางออกที่ดี ฉันจะใช้ถ้อยคำแสดงความคิดเห็นเป็นการพูดว่า: หากคุณไม่สามารถพึ่งพา ~ เป็นไม่ได้ใช้แทนที่เหตุการณ์ปัจจุบันโดยใช้ s / ~ / VERYspeciaL / g จากนั้นทำเคล็ดลับข้างต้นแล้วส่งต้นฉบับ ~ โดยใช้ s / VERYspeciaL / ~ / g
ishahak

1
ฉันมักจะชอบใช้ "ตัวแปร" rarer สำหรับสิ่งนี้ดังนั้นแทนที่จะใช้`ฉัน<$$>(ตั้งแต่$$ขยาย ID กระบวนการของคุณในเชลล์แม้ว่าคุณจะต้องใช้เครื่องหมายคำพูดคู่แทนที่จะเป็นเครื่องหมายคำพูดเดี่ยวและนั่น อาจแบ่งส่วนอื่น ๆ ของ regex ของคุณ) หรือหาก Unicode <∈∋>ใช้ได้บางอย่างเช่น
Adam Katz

เมื่อถึงจุดหนึ่งคุณต้องถามตัวเองว่าทำไมคุณถึงไม่ใช้perlหรือใช้pythonภาษาอื่นแทน perlทำสิ่งนี้ในลักษณะที่เปราะบางน้อยกว่าในบรรทัดเดียวหรือไม่ ...
ArtOfWarfare

18

การจับคู่ที่ไม่โลภโดย Christoph Sieghart

เคล็ดลับในการจับคู่ที่ไม่โลภในการจับคู่คือการจับคู่ตัวละครทั้งหมดยกเว้นตัวที่จบการแข่งขัน ฉันรู้ว่าไม่มีเกมง่ายๆ แต่ฉันได้สูญเสียเวลาอันมีค่าไปกับมันและสคริปต์เชลล์ควรเป็นไปอย่างรวดเร็วและง่ายดาย ดังนั้นในกรณีที่คนอื่นอาจต้องการ:

การจับคู่โลภ

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

การจับคู่ที่ไม่โลภ

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar

17

สิ่งนี้สามารถทำได้โดยใช้การตัด:

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3

9

อีกวิธีหนึ่งที่ไม่ได้ใช้ regex คือใช้วิธี field / delimiter เช่น

string="http://www.suepearson.co.uk/product/174/71/3816/"
echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"

5

sed มีสถานที่อย่างแน่นอน แต่นี่ไม่ใช่หนึ่งในนั้น!

ในฐานะที่ดีมีการชี้: cutใช้งานเพียงแค่ มันง่ายกว่าและปลอดภัยกว่ามากในกรณีนี้ นี่คือตัวอย่างที่เราแยกส่วนประกอบต่าง ๆ จาก URL โดยใช้ไวยากรณ์ Bash:

url="http://www.suepearson.co.uk/product/174/71/3816/"

protocol=$(echo "$url" | cut -d':' -f1)
host=$(echo "$url" | cut -d'/' -f3)
urlhost=$(echo "$url" | cut -d'/' -f1-3)
urlpath=$(echo "$url" | cut -d'/' -f4-)

ให้คุณ:

protocol = "http"
host = "www.suepearson.co.uk"
urlhost = "http://www.suepearson.co.uk"
urlpath = "product/174/71/3816/"

อย่างที่คุณเห็นนี่เป็นวิธีที่ยืดหยุ่นกว่า

(เครดิตทั้งหมดให้กับ Dee)


3
sed 's|(http:\/\/[^\/]+\/).*|\1|'

1
ถ้าคุณใช้ "|" ในฐานะที่เป็นตัวคั่นของคุณไม่จำเป็นต้องหลบหนี "/"
Michael Back

3

sed -E ตีความนิพจน์ทั่วไปเป็นนิพจน์ปกติแบบขยาย (ทันสมัย)

ปรับปรุง: -E บน MacOS X, -r ใน GNU sed


4
ไม่มันไม่ ... อย่างน้อยก็ไม่ GNU sed
Michel de Ruiter

7
ในวงกว้างมากขึ้น-Eเป็นเอกลักษณ์ของ BSD sedและดังนั้น OS X. ลิงก์ไปยังหน้าคน -rไม่นำนิพจน์ทั่วไปที่ขยายไปยังGNUsedดังที่ระบุไว้ในการแก้ไขของ @ stephancheg ระวังเมื่อใช้คำสั่งของความแปรปรวนที่เป็นที่รู้จักในการแจกแจง 'ระวัง' ฉันเรียนรู้วิธีที่ยากลำบาก
fny

1
นี่เป็นคำตอบที่ถูกต้องหากคุณต้องการใช้ sed และเป็นคำถามที่เหมาะสมที่สุดสำหรับคำถามเริ่มต้น
Will Tice

8
-rตัวเลือกของ GNU sed เปลี่ยนแปลงกฎการหลบหนีเท่านั้นตามAppendix A Extended regular expressionsไฟล์ข้อมูลและการทดสอบอย่างรวดเร็ว มันไม่ได้เพิ่มรอบคัดเลือกที่ไม่ใช่ความโลภ ( GNU sed version 4.2.1อย่างน้อยก็)
eichin

1
GNU sed ได้รับการยอมรับ-Eในฐานะที่เป็นตัวเลือกที่ไม่มีเอกสารในขณะที่ แต่ในการเปิดตัว 4.2.2.177เอกสารที่ได้รับการปรับปรุงเพื่อสะท้อนให้เห็นว่าเพื่อให้-Eเป็นที่ดีสำหรับทั้งในปัจจุบัน
Benjamin W.

3

ยังคงมีความหวังที่จะแก้ปัญหานี้ด้วยการใช้ pure (GNU) sed แม้จะไม่ใช่โซลูชันทั่วไปในบางกรณีคุณสามารถใช้ "ลูป" เพื่อกำจัดส่วนที่ไม่จำเป็นทั้งหมดของสตริงเช่นนี้:

sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
  • -r: ใช้ Extended regex (สำหรับ + ​​และวงเล็บที่ไม่ใช้ค่า Escape)
  • ": loop": กำหนดป้ายกำกับใหม่ชื่อ "loop"
  • -e: เพิ่มคำสั่งเพื่อ sed
  • "t loop": ข้ามกลับไปที่ป้ายกำกับ "loop" หากมีการทดแทนที่สำเร็จ

ปัญหาเดียวที่นี่คือมันจะตัดอักขระตัวคั่นสุดท้าย ('/') แต่ถ้าคุณต้องการมันจริงๆคุณสามารถนำกลับมาได้หลังจาก "วน" เสร็จสิ้นเพียงแค่ผนวกคำสั่งเพิ่มเติมนี้ที่ส่วนท้ายของหน้าที่แล้ว บรรทัดคำสั่ง:

-e "s,$,/,"

2

เนื่องจากคุณระบุว่าคุณกำลังพยายามใช้ sed (แทน perl, cut, ฯลฯ ) ให้ลองจัดกลุ่ม สิ่งนี้เป็นการหลีกเลี่ยงตัวระบุที่ไม่โลภซึ่งอาจไม่ได้รับการยอมรับ กลุ่มแรกคือโปรโตคอล (เช่น 'http: //', 'https: //', 'tcp: //', ฯลฯ ) กลุ่มที่สองคือโดเมน:

echo "http://www.suon.co.uk/product/1/7/3/" | sed "s | ^ \ (. * // \) \ ([^ /] * \). * $ | \ 1 \ 2 |"

หากคุณไม่คุ้นเคยกับการจัดกลุ่มเริ่มต้นที่นี่


1

ฉันรู้ว่านี่เป็นรายการเก่า แต่บางคนอาจพบว่ามีประโยชน์ เนื่องจากชื่อโดเมนแบบเต็มต้องไม่เกินความยาวทั้งหมด 253 อักขระแทนที่ * ด้วย. \ {1, 255 \}


1

นี่คือวิธีการจับคู่สายอักขระหลายตัวแบบไม่โลภอย่างแน่นหนาโดยใช้ sed ช่วยบอกว่าคุณต้องการที่จะเปลี่ยนทุกfoo...barการ<foo...bar>ดังนั้นสำหรับตัวอย่างเช่นการป้อนข้อมูลนี้:

$ cat file
ABC foo DEF bar GHI foo KLM bar NOP foo QRS bar TUV

ควรเป็นผลลัพธ์นี้:

ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

ในการทำเช่นนั้นคุณจะแปลง foo และ bar เป็นอักขระแต่ละตัวจากนั้นใช้การปฏิเสธของอักขระเหล่านั้นระหว่างพวกเขา:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/g; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

ในด้านบน:

  1. s/@/@A/g; s/{/@B/g; s/}/@C/gกำลังแปลง{และ}สตริงตัวยึดตำแหน่งที่ไม่สามารถมีอยู่ในอินพุตดังนั้นตัวอักษรเหล่านั้นจะพร้อมใช้งานสำหรับการแปลงfooและbarถึง
  2. s/foo/{/g; s/bar/}/gกำลังแปลงfooและbarไปยัง{และ}ตามลำดับ
  3. s/{[^{}]*}/<&>/gกำลังดำเนินการ op ที่เราต้องการ - แปลงfoo...barเป็น<foo...bar>
  4. s/}/bar/g; s/{/foo/gคือการแปลง{และ}กลับไปและfoobar
  5. s/@C/}/g; s/@B/{/g; s/@A/@/g กำลังแปลงสตริงตัวยึดตำแหน่งกลับไปเป็นอักขระดั้งเดิม

โปรดทราบว่าข้างต้นไม่พึ่งพาสายอักขระใด ๆ ที่ไม่มีอยู่ในอินพุตเนื่องจากสตริงดังกล่าวผลิตในขั้นตอนแรกและไม่สนใจว่า regexp ใด ๆ ที่คุณต้องการจับคู่เกิดขึ้นเนื่องจากคุณสามารถใช้งานได้{[^{}]*}บ่อยครั้งเท่าที่จำเป็น ในนิพจน์เพื่อแยกการจับคู่ที่แท้จริงที่คุณต้องการและ / หรือด้วยตัวดำเนินการจับคู่ตัวเลขเช่นเพื่อแทนที่เหตุการณ์ที่สองเท่านั้น:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/2; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC foo DEF bar GHI <foo KLM bar> NOP foo QRS bar TUV

1

ยังไม่เห็นคำตอบนี้ดังนั้นนี่เป็นวิธีที่คุณสามารถทำได้ด้วยviหรือvim:

vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null

สิ่งนี้รันการvi :%sแทนที่แบบโกลบอล (ต่อท้ายg), ละเว้นจากการเพิ่มข้อผิดพลาดหากไม่พบรูปแบบ ( e) จากนั้นบันทึกการเปลี่ยนแปลงผลลัพธ์ลงในดิสก์และออก การ&>/dev/nullป้องกัน GUI จากการกระพริบบนหน้าจอสั้น ๆ ซึ่งอาจทำให้รำคาญ

ฉันชอบใช้viบางครั้งสำหรับ regexes ที่ซับซ้อนมากเพราะ (1) perl ตายแล้ว (2) vim มีเครื่องมือ regex ขั้นสูงมากและ (3) ฉันคุ้นเคยกับ regex viในการแก้ไขการใช้งานประจำวัน เอกสาร


0
echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'

ไม่รำคาญฉันได้รับมันในฟอรั่มอื่น :)


4
ดังนั้นคุณจะได้รับการจับคู่โลภ: /home/one/two/three/ถ้าคุณเพิ่มอีก/เช่น/home/one/two/three/four/myfile.txtคุณจะจับคู่โลภfourเช่นกัน: /home/one/two/three/fourคำถามคือเกี่ยวกับที่ไม่ใช่โลภ
stefanB


0

นี่คือสิ่งที่คุณสามารถทำได้ด้วยวิธีการสองขั้นตอนและ awk:

A=http://www.suepearson.co.uk/product/174/71/3816/  
echo $A|awk '  
{  
  var=gensub(///,"||",3,$0) ;  
  sub(/\|\|.*/,"",var);  
  print var  
}'  

ผลลัพธ์: http://www.suepearson.co.uk

หวังว่าจะช่วย!


0

รุ่นอื่นอีก:

sed 's|/[:alnum:].*||' file.txt

มันจับคู่/ตามด้วยตัวอักษรและตัวเลข (ไม่ใช่สแลชต่อไปอีก) รวมถึงอักขระที่เหลือจนถึงจุดสิ้นสุดของบรรทัด หลังจากนั้นจะแทนที่ด้วยไม่มีอะไร (เช่นลบออก)


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