จะลดความโลภของนิพจน์ทั่วไปใน AWK ได้อย่างไร?


14

ฉันต้องการจะทำรูปแบบไม่โลภ (regular expression) awkการจับคู่ใน นี่คือตัวอย่าง:

echo "@article{gjn, Author =   {Grzegorz J. Nalepa}, " | awk '{ sub(/@.*,/,""); print }'

เป็นไปได้ไหมที่จะเขียนนิพจน์ทั่วไปที่เลือกสตริงที่สั้นกว่า?

@article{gjn,

แทนที่จะเป็นสายยาวนี้:

@article{gjn, Author =   {Grzegorz J. Nalepa},

ฉันต้องการผลลัพธ์นี้:

 Author =   {Grzegorz J. Nalepa},



ฉันมีตัวอย่างอื่น:

echo " ,บทความ {gjn, ผู้แต่ง = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); พิมพ์} '
      ↑↑ ^^^^^

โปรดทราบว่าฉันเปลี่ยน@อักขระเป็นอักขระจุลภาค ( ,) ในตำแหน่งแรกของทั้งสตริงอินพุตและนิพจน์ทั่วไป (และเปลี่ยน.*เป็น[^,]*) เป็นไปได้ไหมที่จะเขียนนิพจน์ทั่วไปที่เลือกสตริงที่สั้นกว่า?

, Author =   {Grzegorz J. Nalepa},

แทนที่จะเป็นสตริงที่ยาวกว่า?:

,article{gjn, Author =   {Grzegorz J. Nalepa},

ฉันต้องการผลลัพธ์นี้:

,article{gjn

4
regex ไม่เพียงพอสำหรับการแยกวิเคราะห์ HTML ที่มีประสิทธิภาพพวกเขาอาจจะไม่สามารถทำการแยกวิเคราะห์ไวยากรณ์ตามบริบทได้ อย่างไรก็ตามหากชุดอินพุตของคุณนั้นถูก จำกัด และมีรูปแบบที่ดีคุณอาจสามารถใช้ regex ได้ตราบใดที่คุณประกาศว่าข้อ จำกัด ของคุณคืออะไร ตัวอย่างเช่นคุณสามารถมองหาAuthorเครื่องหมายจุลภาคและช่องว่างตามด้วยช่องว่างตามด้วยช่องว่างตามด้วย=ตามด้วยช่องว่างตามด้วย{ไม่ใช่}ตามด้วย}แม้ว่าสิ่งนี้จะต้อง (ในสิ่งอื่น ๆ ) ที่คุณไม่สามารถทำรัง{}ใน= { ... }ส่วน
jw013

@ jw013 ขอบคุณสำหรับคำอธิบายของคุณ แต่ฉันจะรอคำแนะนำจากผู้ใช้รายอื่น
nowy1

คำตอบ:


18

หากคุณต้องการเลือก@และมากถึงลำดับแรก,หลังจากนั้นคุณต้องระบุเป็น@[^,]*,

นั่นคือ@ตามด้วยหมายเลขใด ๆ ( *) ไม่ใช่เครื่องหมายจุลภาค ( [^,]) ตามด้วยเครื่องหมายจุลภาค ( ,)

วิธีการนี้ใช้งานได้เทียบเท่า@.*?,แต่ไม่ใช่สำหรับสิ่งที่ต้องการ@.*?stringนั่นคือสิ่งที่หลังจากนั้นมีมากกว่าตัวละครตัวเดียว กวนตัวอักษรเป็นเรื่องง่าย แต่กวนสตริงใน regexps เป็นจำนวนมากยากมากขึ้น

วิธีที่แตกต่างคือการประมวลผลอินพุตล่วงหน้าของคุณเพื่อแทนที่หรือเสริมstringด้วยอักขระที่ไม่ได้เกิดขึ้นในอินพุตของคุณ:

gsub(/string/, "\1&") # pre-process
gsub(/@[^\1]*\1string/, "")
gsub(/\1/, "") # revert the pre-processing

หากคุณไม่สามารถรับประกันได้ว่าข้อมูลที่ป้อนจะไม่มีอักขระทดแทน ( \1ด้านบน) วิธีหนึ่งคือใช้กลไกการหลบหนี:

gsub(/\1/, "\1\3") # use \1 as the escape character and escape itself as \1\3
                   # in case it's present in the input
gsub(/\2/, "\1\4") # use \2 as our maker character and escape it
                   # as \1\4 in case it's present in the input
gsub(/string/, "\2&") # mark the "string" occurrences

gsub(/@[^\2]*\2string/, "")

# then roll back the marking and escaping
gsub(/\2/, "")
gsub(/\1\4/, "\2")
gsub(/\1\3/, "\1")

ผลงานที่ได้รับการแก้ไขstrings แต่ไม่ได้สำหรับ regexps @.*?foo.barพลเหมือนเทียบเท่า


ขอบคุณมากสำหรับคำตอบที่ดี ในการแก้ไขของฉันฉันถามอีกตัวอย่างหนึ่ง (ดูการแก้ไขของฉัน)
nowy1

6

มีคำตอบที่ดีอยู่แล้วหลายประการที่ให้วิธีแก้ปัญหาสำหรับการawkไม่สามารถจับคู่ที่ไม่โลภดังนั้นฉันจึงให้ข้อมูลบางอย่างเกี่ยวกับวิธีอื่นในการทำโดยใช้Perl Compatible Regular Expressions (PCRE) โปรดทราบว่าawkสคริปต์"จับคู่และพิมพ์" ที่ง่ายที่สุดสามารถนำมาใช้ใหม่ได้อย่างง่ายดายในการperlใช้-nตัวเลือกบรรทัดคำสั่งและสคริปต์ที่ซับซ้อนมากขึ้นสามารถแปลงด้วยa2p Awk เป็น Perl translator

Perlมีตัวดำเนินการที่ไม่โลภซึ่งสามารถใช้ในสคริปต์ Perl และทุกอย่างที่ใช้ PCRE ตัวอย่างเช่นนำไปใช้ใน-Pตัวเลือกgrep ของ GNU

PCRE ไม่เหมือนกับนิพจน์ปกติของ Perl แต่ใกล้เคียงกันมาก มันเป็นตัวเลือกยอดนิยมของไลบรารีนิพจน์ทั่วไปสำหรับหลาย ๆ โปรแกรมเพราะมันเร็วมากและการปรับปรุง Perl ของนิพจน์ทั่วไปที่มีประโยชน์นั้นมีประโยชน์มาก

จากหน้า man perlre (1) :

   By default, a quantified subpattern is "greedy", that is, it will match
   as many times as possible (given a particular starting location) while
   still allowing the rest of the pattern to match.  If you want it to
   match the minimum number of times possible, follow the quantifier with
   a "?".  Note that the meanings don't change, just the "greediness":

       *?        Match 0 or more times, not greedily
       +?        Match 1 or more times, not greedily
       ??        Match 0 or 1 time, not greedily
       {n}?      Match exactly n times, not greedily (redundant)
       {n,}?     Match at least n times, not greedily
       {n,m}?    Match at least n but not more than m times, not greedily

3

นี่คือโพสต์เก่า แต่ข้อมูลต่อไปนี้อาจเป็นประโยชน์สำหรับผู้อื่น

มีวิธีหนึ่งที่เป็นที่ยอมรับกันว่าน้ำมันดิบเพื่อทำการจับคู่ RE ที่ไม่โลภใน awk แนวคิดพื้นฐานคือการใช้ฟังก์ชั่นการจับคู่ (สตริง, RE) และลดขนาดของสตริงอย่างต่อเนื่องจนกว่าการแข่งขันจะล้มเหลวสิ่งที่ต้องการ (ยังไม่ทดลอง):

if (match(string, RE)) {
    rstart = RSTART
    for (i=RLENGTH; i>=1; i--)
        if (!(match(substr(string,1,rstart+i-1), RE))) break;
    # At this point, the non-greedy match will start at rstart
    #  for a length of i+1
}

2

สำหรับนิพจน์ทั่วไปสามารถใช้เป็นการจับคู่ที่ไม่โลภได้:

function smatch(s, r) {
    if (match(s, r)) {
        m = RSTART
        do {
            n = RLENGTH
        } while (match(substr(s, m, n - 1), r))
        RSTART = m
        RLENGTH = n
        return RSTART
    } else return 0
}

ฉันใช้สิ่งนี้ตามคำตอบของ @ JimMellander smatchทำตัวเหมือนmatchกลับมา:

ตำแหน่ง s ที่นิพจน์ทั่วไปrเกิดขึ้นหรือ 0 หากไม่เป็นเช่นนั้น ตัวแปรRSTARTและRLENGTHถูกตั้งค่าเป็นตำแหน่งและความยาวของสตริงที่ตรงกัน


1

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

awk '{ sub(/@.*Author/,"Author"); print }'

หากจำนวนอักขระก่อนหน้าผู้แต่งเหมือนกันเสมอคุณสามารถทำได้:

awk '{ sub(/@.{21}/,""); print }'

คุณเพียงแค่ต้องรู้ว่าข้อมูลของคุณเป็นอย่างไรในทั้งชุด


0

มีวิธีเสมอ ปัญหาที่ให้สามารถแก้ไขได้อย่างง่ายดายโดยใช้เครื่องหมายจุลภาคเป็นตัวคั่น

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk -F, '{sub(/^[ \t]/, "", $2); print $2}'

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

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk  '{sub(/.*Author/, "Author", $0); sub(/},.*/, "}", $0); print $0}'

0

ฉันรู้ว่านี่เป็นโพสต์เก่า แต่นี่คือสิ่งที่ใช้ awk เป็น OP ตามที่ร้องขอ:
A = @ article {gjn2010jucs, ผู้แต่ง = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")

เอาต์พุต:
, ผู้แต่ง = {Grzegorz J. Nalepa},


1
คำตอบนั้นผิดด้วยเหตุผลห้าข้อ
สกอตต์

3
คุณกรุณาช่วยฉันเข้าใจว่ามีอะไรผิดปกติหรือไม่? ผลลัพธ์ดูเหมือนสอดคล้องกับสิ่งที่ร้องขอ พยายามเข้าใจว่าทำไมคำตอบนั้นถูก / ไม่ถูกต้อง
VINAY NAIR
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.