ทำไมหรือทำไมจึงใช้ `. *?` ดีกว่า '. *'?


9

ฉันตอบคำถามนี้ใน SuperUserซึ่งเป็นสิ่งที่เกี่ยวข้องกับประเภทของนิพจน์ทั่วไปที่ใช้ในขณะที่ทำการพิมพ์ออกมา

คำตอบที่ฉันให้คือ:

 tail -f log | grep "some_string.*some_string"

จากนั้นในสามความคิดเห็นต่อคำตอบของฉัน@Bobเขียนสิ่งนี้:

.*เป็นโลภและอาจจับภาพมากกว่าที่คุณต้องการ .*?มักจะดีกว่า

ถ้าอย่างนั้น

the ?เป็นตัวดัดแปลง*ทำให้มันขี้เกียจแทนที่จะเป็นค่าเริ่มต้นโลภ สมมติว่า PCRE

ฉันไปหาPCREแต่ไม่สามารถเข้าใจความหมายของคำตอบนี้ได้?

และในที่สุดนี้

ฉันควรชี้ให้เห็นว่านี่คือ regex (grep ทำ POSIX regex โดยปริยาย) ไม่ใช่ shell glob

ฉันรู้ว่า Regex คืออะไรและใช้งานพื้นฐานมากเพียงใดในคำสั่ง grep ดังนั้นฉันไม่สามารถรับความคิดเห็นทั้งสามข้อเหล่านี้ได้และฉันมีคำถามเหล่านี้อยู่ในใจ:

  • อะไรคือความแตกต่างในการใช้งานของ.*?vs. .*?
  • ไหนดีกว่าและภายใต้สถานการณ์ใด กรุณาให้ตัวอย่าง

นอกจากนี้ยังจะเป็นประโยชน์ในการทำความเข้าใจความคิดเห็นถ้าใครทำได้


UPDATE: เพื่อเป็นคำตอบสำหรับคำถามRegex ต่างจาก Shell Globs อย่างไร @Kusalanandaระบุลิงก์นี้ในความคิดเห็นของเขา

หมายเหตุ: หากจำเป็นโปรดอ่านคำตอบของฉันสำหรับคำถามนี้ก่อนตอบเพื่ออ้างอิงถึงบริบท


นี่เป็นคำถามสองข้อที่แตกต่างกันมาก คำถามแรกนั้นตอบโดยunix.stackexchange.com/questions/57957/ขณะที่คำถามที่สองนั้นขึ้นอยู่กับการประยุกต์ใช้รูปแบบ (ไม่สามารถพูดได้ว่าเป็น "ดีกว่า" ในทุกสถานการณ์)
Kusalananda

คุณสามารถแก้ไขคำถามนี้ให้เป็นเรื่องเกี่ยว.*กับ.*?ปัญหา คำถาม "ความแตกต่างระหว่างนิพจน์ทั่วไปและเปลือกโกลเบลนั้นได้รับการแก้ไขแล้วในไซต์นี้
Kusalananda

คำตอบ:


7

Ashok ชี้ให้เห็นถึงความแตกต่างระหว่าง.*และ.*?ดังนั้นฉันจะให้ข้อมูลเพิ่มเติม

grep (สมมติว่าเป็นเวอร์ชั่น GNU) รองรับ 4 วิธีในการจับคู่สตริง:

  • สตริงคงที่
  • นิพจน์ปกติพื้นฐาน (BRE)
  • นิพจน์ทั่วไปที่ขยายเพิ่ม (ERE)
  • นิพจน์ทั่วไปที่เข้ากันได้กับ Perl (PCRE)

grep ใช้ BRE เป็นค่าเริ่มต้น

BRE และ ERE จะถูกบันทึกไว้ในการแสดงผลปกติบทของ POSIX และ PCRE การบันทึกไว้ในของเว็บไซต์อย่างเป็นทางการ โปรดทราบว่าคุณสมบัติและไวยากรณ์อาจแตกต่างกันระหว่างการนำไปใช้งาน

เป็นมูลค่าที่บอกว่าทั้ง BRE และ ERE ไม่สนับสนุนความขี้เกียจ :

พฤติกรรมของสัญลักษณ์การทำซ้ำหลายรายการติดกัน ('+', '*', '?' และช่วงเวลา) สร้างผลลัพธ์ที่ไม่ได้กำหนด

ดังนั้นหากคุณต้องการใช้คุณสมบัตินั้นคุณจะต้องใช้ PCRE แทน:

# BRE greedy
$ grep -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# BRE lazy
$ grep -o 'c.*\?s' <<< 'can cats eat plants?'
can cats eat plants

# ERE greedy
$ grep -E -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# ERE lazy
$ grep -E -o 'c.*?s' <<< 'can cats eat plants?'
can cats eat plants

# PCRE greedy
$ grep -P -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# PCRE lazy
$ grep -P -o 'c.*?s' <<< 'can cats eat plants?'
can cats

แก้ไข 1

คุณช่วยอธิบายเล็กน้อยเกี่ยวกับ.*vs ได้.*?ไหม?

  • .*ใช้เพื่อจับคู่กับรูปแบบ"ยาวที่สุด" 1 ที่เป็นไปได้

  • .*?ใช้เพื่อจับคู่รูปแบบ"shortest" 1 ที่เป็นไปได้

จากประสบการณ์ของฉันพฤติกรรมที่ต้องการมากที่สุดมักจะเป็นพฤติกรรมที่สอง

ตัวอย่างเช่นสมมติว่าเรามีสตริงต่อไปนี้และเราต้องการจับคู่กับแท็ก html 2เท่านั้นไม่ใช่เนื้อหาระหว่างพวกเขา:

<title>My webpage title</title>

ตอนนี้เปรียบเทียบ.*กับ.*?:

# Greedy
$ grep -P -o '<.*>' <<< '<title>My webpage title</title>'
<title>My webpage title</title>

# Lazy
$ grep -P -o '<.*?>' <<< '<title>My webpage title</title>'
<title>
</title>

1. ความหมายของ "ยาวที่สุด" และ "ที่สั้นที่สุด" ในบริบท regex เป็นบิตหากินเป็น Kusalananda ชี้ให้เห็น อ้างถึงเอกสารทางการสำหรับข้อมูลเพิ่มเติม
2. มันไม่ได้แนะนำให้แยก HTML ที่มี regex นี่เป็นเพียงตัวอย่างเพื่อการศึกษาอย่าใช้ในการผลิต


คุณช่วยอธิบายเล็กน้อยเกี่ยวกับ.*vs ได้.*?ไหม?
C0deDaedalus

@ C0deDaedalus อัปเดตแล้ว
nxnev

9

สมมติว่าฉันใช้สายเหมือน:

can cats eat plants?

การใช้ความโลภc.*sจะจับคู่สตริงทั้งหมดตั้งแต่เริ่มต้นcและสิ้นสุดด้วยsการเป็นผู้ประกอบการโลภจะยังคงจับคู่จนกระทั่งเกิดเหตุการณ์สุดท้ายของ s

ในขณะที่การใช้ขี้เกียจc.*?sจะตรงกับจนกว่าจะเกิดขึ้นครั้งแรกของการพบคือสตริงscan cats

จากตัวอย่างด้านบนคุณอาจรวบรวมได้:

"โลภ" หมายถึงการจับคู่สตริงที่ยาวที่สุดที่เป็นไปได้ "Lazy" หมายถึงการจับคู่สตริงที่สั้นที่สุด เพิ่ม?ไปปริมาณเช่น*, +, ?หรือ{n,m}ทำให้มันขี้เกียจ


1
"สั้นที่สุดเท่าที่จะเป็นไปได้" catsดังนั้นจึงไม่บังคับ "สั้นที่สุด" อย่างเคร่งครัดในแง่นั้น
Kusalananda

2
@ Kusalananda จริงไม่อย่างเคร่งครัดในแง่นั้น แต่ "สั้นที่สุด" ที่นี่หมายถึงระหว่างการเกิดขึ้นครั้งแรกของทั้ง c และ s
Ashok Arora

1

สตริงสามารถจับคู่ได้หลายวิธี (จากง่ายไปจนถึงซับซ้อนกว่า):

  1. เป็นสตริงแบบคงที่ (สมมติ var = 'Hello World!'):

    [ "$var" = "Hello World!" ] && echo yes
    echo "$var" | grep -F "Hello"
    grep -F "Hello" <<<"$var"

  2. ในฐานะกลม:

    echo ./* # แสดงรายการไฟล์ทั้งหมดใน pwd
    case $var in (*Worl*) echo yes;; (*) echo no;; esac
    [[ "$var" == *"Worl"* ]] && echo yes

    มีเงาพื้นฐานและขยาย caseตัวอย่างเช่นการใช้ globs พื้นฐาน [[ตัวอย่างทุบตีใช้ globs ขยาย การจับคู่ไฟล์แรกอาจเป็นพื้นฐานหรือขยายบนเชลล์บางตัวเช่นการตั้งค่าextglobใน bash ทั้งสองเหมือนกันในกรณีนี้ Grep ไม่สามารถใช้ globs

    เครื่องหมายดอกจันในglobหมายถึงบางสิ่งที่แตกต่างจากเครื่องหมายดอกจันในregex :

    ตัวอักษรใด* matches any number (including none) of
    องค์ประกอบก่อน* matches any number (including none) of the

  3. เป็นนิพจน์ทั่วไปพื้นฐาน (BRE):

    echo "$var" | sed 's/W.*d//' # พิมพ์: Hello!
    grep -o 'W.*d' <<<"$var" # print World!

    ไม่มี BRE ใน (พื้นฐาน) เชลล์หรือ awk

  4. ขยายการแสดงออกปกติ (ERE):

    [[ "$var" =~ (H.*l) ]] # match: Hello Worl
    echo "$var" | sed -E 's/(d|o)//g' # print: Hell Wrl!
    awk '/W.*d/{print $1}' <<<"$var" # print: สวัสดี
    grep -oE 'H.*l' <<<"$var" # print: สวัสดี Worl

  5. นิพจน์ปกติที่เข้ากันได้ของ Perl:

    grep -oP 'H.*?l # print: เฮล

เฉพาะใน PCRE a เท่านั้นที่*?มีความหมายของไวยากรณ์เฉพาะ
มันทำให้ดอกจันขี้เกียจ (ungreedy): ความเกียจคร้านแทนที่จะตะกละ

$ grep -oP 'e.*l' <<<"$var"
ello Worl

$ grep -oP 'e.*?l' <<<"$var"
el

นี่เป็นเพียงยอดของภูเขาน้ำแข็งที่มีโลภขี้เกียจและอ่อนน้อมหรือ possesive นอกจากนี้ยังมีlookahead และ lookbehind*แต่ผู้ที่ไม่สามารถใช้กับเครื่องหมายดอกจัน

มีทางเลือกอื่นเพื่อให้ได้ผลเช่นเดียวกับ regex ที่ไม่โลภ:

$ grep -o 'e[^o]*o' <<<"$var"
ello

ความคิดที่ง่ายมาก: ไม่ได้ใช้จุด., [^o]ลบล้างตัวอักษรถัดไปจะมีการแข่งขัน ด้วยแท็กเว็บ:

$ grep -o '<[^>]*>' <<<'<script type="text/javascript">document.write(5 + 6);</script>'
<script type="text/javascript">
</script>

ด้านบนควรอธิบายความคิดเห็น @Bob 3 ทั้งหมดอย่างสมบูรณ์ ถอดความ:

  • A. * เป็น regex ทั่วไปไม่ใช่ glob
  • regex เท่านั้นที่สามารถใช้งานร่วมกับ PCRE
  • ใน PCRE: a? แก้ไข * quantifier .*ความโลภ.*?ไม่ใช่

คำถาม

  • ความแตกต่างในการใช้งานของคืออะไร ? กับ. ?

    • A .*?ใช้ได้ในไวยากรณ์ PCRE เท่านั้น
    • A .*สามารถพกพาได้มากกว่า
    • เอฟเฟกต์แบบเดียวกันกับการจับคู่แบบไม่โลภสามารถทำได้โดยการแทนที่จุดด้วยช่วงอักขระที่ไม่ได้แสดง: [^a]*
  • ไหนดีกว่าและภายใต้สถานการณ์ใด กรุณาให้ตัวอย่าง
    ดีขึ้นหรือไม่ มันขึ้นอยู่กับเป้าหมาย ไม่ดีกว่ากันแต่ละอันมีประโยชน์สำหรับวัตถุประสงค์ที่ต่างกัน ฉันได้ให้ตัวอย่างหลายอย่างข้างต้น คุณต้องการมากกว่านี้ไหม?

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