Bagaimana atau Mengapa menggunakan `. *?` Lebih baik daripada `. *`?

9

Saya menjawab pertanyaan ini pada SuperUser yang merupakan sesuatu yang berkaitan dengan jenis ekspresi Reguler yang digunakan saat menerima keluaran.

Jawaban yang saya berikan adalah ini:

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

Dan kemudian, Dalam tiga komentar untuk jawaban saya @ Bob menulis ini:

.*serakah dan mungkin menangkap lebih dari yang Anda inginkan. .*?biasanya lebih baik.

Lalu ini,

itu ?adalah pengubah aktif *, menjadikannya malas bukannya default serakah. Dengan asumsi PCRE.

Saya mencari di Google PCRE, tetapi tidak bisa mendapatkan apa arti dari ini dalam jawaban saya?

dan akhirnya ini,

Saya juga harus menunjukkan bahwa ini adalah regex (grep melakukan POSIX regex secara default), bukan shell glob.

Saya hanya tahu apa itu Regex dan penggunaannya yang sangat mendasar dalam perintah grep. Jadi, saya tidak bisa mendapatkan salah satu dari 3 komentar itu dan saya memiliki pertanyaan ini dalam pikiran:

  • Apa perbedaan dalam penggunaan .*?vs .*?
  • Mana yang lebih baik dan dalam keadaan apa? Tolong berikan contoh.

Juga akan sangat membantu untuk memahami komentar, Jika ada yang bisa


PEMBARUAN: Sebagai jawaban atas pertanyaan. Apa perbedaan Regex dari Shell Glob? @ Kusalananda memberikan tautan ini dalam komentarnya.

CATATAN: Jika perlu, Harap baca jawaban saya untuk pertanyaan ini sebelum menjawab untuk merujuk pada konteks.

C0deDaedalus
sumber
Ini adalah dua pertanyaan yang sangat berbeda. Pertanyaan pertama dijawab oleh unix.stackexchange.com/questions/57957/… sementara pertanyaan kedua tergantung pada penerapan pola (tidak bisa dikatakan "lebih baik" dalam semua keadaan).
Kusalananda
Anda dapat mengedit pertanyaan ini hanya tentang masalah .*vs. .*?Pertanyaan "perbedaan antara ekspresi reguler dan gumpalan shell" telah ditanggapi di situs ini.
Kusalananda

Jawaban:

7

Ashok sudah menunjukkan perbedaan antara .*dan .*?, jadi saya hanya akan memberikan beberapa informasi tambahan.

grep (dengan asumsi versi GNU) mendukung 4 cara untuk mencocokkan string:

  • Memperbaiki string
  • Ekspresi reguler dasar (BRE)
  • Ekspresi reguler yang diperluas (ERE)
  • Ekspresi reguler yang kompatibel dengan Perl (PCRE)

grep menggunakan BRE secara default.

BRE dan ERE didokumentasikan dalam bab Ekspresi Reguler POSIX dan PCRE didokumentasikan di situs resminya . Harap dicatat bahwa fitur dan sintaksis dapat bervariasi di antara implementasi.

Layak dikatakan bahwa BRE atau ERE tidak mendukung kemalasan :

Perilaku beberapa simbol duplikasi yang berdekatan ('+', '*', '?', Dan interval) menghasilkan hasil yang tidak ditentukan.

Jadi jika Anda ingin menggunakan fitur itu, Anda harus menggunakan PCRE sebagai gantinya:

# 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

Edit 1

Bisakah Anda jelaskan sedikit tentang .*vs .*??

  • .*digunakan untuk mencocokkan dengan pola "terpanjang" 1 yang mungkin.

  • .*?digunakan untuk mencocokkan pola "terpendek" 1 mungkin.

Dalam pengalaman saya, perilaku yang paling dicari biasanya yang kedua.

Misalnya, katakanlah kami memiliki string berikut dan kami hanya ingin mencocokkan tag html 2 , bukan konten di antara mereka:

<title>My webpage title</title>

Sekarang bandingkan .*vs .*?:

# 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. Arti "terpanjang" dan "terpendek" dalam konteks regex agak rumit, seperti yang ditunjukkan Kusalananda . Rujuk ke dokumentasi resmi untuk informasi lebih lanjut.
2. Tidak disarankan untuk mem-parsing html dengan regex . Ini hanya contoh untuk tujuan pendidikan, jangan menggunakannya dalam produksi.

nxnev
sumber
Bisakah Anda jelaskan sedikit tentang .*vs .*??
C0deDaedalus
@ C0deDaedalus Diperbarui.
nxnev
9

Misalkan saya mengambil string seperti:

can cats eat plants?

Menggunakan serakah c.*sakan cocok dengan seluruh string sejak dimulai dengan cdan berakhir dengan s, sebagai operator serakah itu terus cocok hingga kemunculan terakhir dari s.

Sedangkan menggunakan lazy c.*?shanya akan cocok sampai kemunculan pertama sditemukan, yaitu string can cats.

Dari contoh di atas, Anda mungkin dapat menyimpulkan bahwa:

"Serakah" berarti mencocokkan string yang terpanjang. "Malas" berarti mencocokkan string sesingkat mungkin. Menambahkan ?ke quantifier seperti *, +, ?, atau {n,m}merek itu malas.

Ashok
sumber
1
"Terpendek mungkin" cats, jadi itu tidak memberlakukan "sesingkat mungkin" secara ketat dalam arti itu.
Kusalananda
2
@ Kusalananda benar, tidak sepenuhnya dalam arti itu tetapi "sesingkat mungkin" di sini berarti antara kejadian pertama dari kedua c dan s.
Ashok
1

String dapat dicocokkan dengan beberapa cara (dari yang sederhana ke yang lebih kompleks):

  1. Sebagai string statis (Asumsikan var = 'Hello World!'):

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

  2. Sebagai gumpalan:

    echo ./* # daftarkan semua file dalam pwd.
    case $var in (*Worl*) echo yes;; (*) echo no;; esac
    [[ "$var" == *"Worl"* ]] && echo yes

    Ada gumpalan dasar dan diperpanjang. The casecontoh menggunakan gumpalan dasar. Contoh bash [[menggunakan glob diperpanjang. Pencocokan file pertama bisa mendasar atau diperluas pada beberapa shell seperti pengaturan extglobdi bash. Keduanya identik dalam hal ini. Grep tidak dapat menggunakan gumpalan.

    Tanda bintang di sebuah bola berarti sesuatu yang berbeda dari tanda bintang di sebuah regex :

    * matches any number (including none) ofsetiap karakter .
    * matches any number (including none) of theelemen sebelumnya .

  3. Sebagai ungkapan reguler dasar (BRE):

    echo "$var" | sed 's/W.*d//' # print: Halo!
    grep -o 'W.*d' <<<"$var" # print World!

    Tidak ada BRE dalam cangkang (dasar) atau awk.

  4. Ekspresi reguler yang diperluas (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: Hello
    grep -oE 'H.*l' <<<"$var" # print: Hello Worl

  5. Ekspresi Reguler Kompatibel yang Kompatibel:

    grep -oP 'H.*?l # print: Hel

Hanya dalam PCRE a *?memiliki beberapa arti sintaksis spesifik.
Itu membuat asterisk malas (ungreedy): Kemalasan Alih-alih Keserakahan .

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

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

Ini hanya puncak gunung es, ada yang serakah, malas , dan jinak atau posesif . Ada juga lookahead dan lookbehind tetapi itu tidak berlaku untuk tanda bintang *.

Ada alternatif untuk mendapatkan efek yang sama dengan regex yang tidak serakah:

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

Idenya sangat sederhana: jangan gunakan titik ., meniadakan karakter berikutnya untuk mencocokkan [^o]. Dengan tag web:

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

Di atas harus sepenuhnya menjelaskan semua komentar @ Bob 3. Parafrase:

  • A. * Adalah regex umum, bukan glob.
  • Hanya regex yang bisa kompatibel dengan PCRE.
  • Di PCRE: a? memodifikasi * quantifier. .*serakah .*?tidak.

Pertanyaan

  • Apa perbedaan dalam penggunaan. ? vs. ?

    • A .*?hanya valid dalam sintaks PCRE.
    • A .*lebih portabel.
    • Efek yang sama dengan kecocokan non-serakah dapat dilakukan dengan mengganti titik dengan rentang karakter yang dinegasikan: [^a]*
  • Mana yang lebih baik dan dalam keadaan apa? Tolong berikan contoh.
    Lebih baik? Itu tergantung tujuannya. Tidak ada yang lebih baik, masing-masing berguna untuk tujuan yang berbeda. Saya telah memberikan beberapa contoh di atas. Apakah kamu butuh lebih?

Ishak
sumber