Bagaimana cara mengurangi ketamakan ekspresi reguler di AWK?


14

Saya ingin melakukan pencocokan pola non-serakah (ekspresi reguler) awk. Berikut ini sebuah contoh:

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

Apakah mungkin untuk menulis ekspresi reguler yang memilih string yang lebih pendek?

@article{gjn,

bukannya string panjang ini ?:

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

Saya ingin mendapatkan hasil ini:

 Author =   {Grzegorz J. Nalepa},



Saya punya contoh lain:

gema " , artikel {gjn, Penulis = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); cetak} '
      ↑ ↑ ^^^^^

Perhatikan bahwa saya mengubah @karakter menjadi koma ( ,) karakter di posisi pertama dari string input dan ekspresi reguler (dan juga berubah .*menjadi [^,]*). Apakah mungkin untuk menulis ekspresi reguler yang memilih string yang lebih pendek?

, Author =   {Grzegorz J. Nalepa},

bukannya string yang lebih panjang ?:

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

Saya ingin mendapatkan hasil ini:

,article{gjn

4
Sama seperti regex yang tidak memadai untuk parsing HTML yang kuat, mereka mungkin tidak akan dapat melakukan parsing tata bahasa konteks-sensitif semacam ini. Namun, jika set input Anda cukup terbatas dan terbentuk dengan baik, Anda mungkin bisa lolos dengan regex selama Anda menyatakan apa batasan Anda. Misalnya Anda bisa mencari Authormengikuti koma dan spasi, diikuti oleh spasi putih diikuti oleh =diikuti oleh spasi putih diikuti oleh {diikuti oleh non- }diikuti oleh }, meskipun ini membutuhkan (antara lain) bahwa Anda tidak bisa bersarang {}di dalam = { ... }bagian.
jw013

@ jw013, terima kasih atas penjelasannya. Namun saya akan menunggu saran dari pengguna lain.
nowy1

Jawaban:


18

Jika Anda ingin memilih @dan hingga yang pertama ,setelah itu, Anda harus menentukannya sebagai@[^,]*,

Yang @diikuti oleh nomor ( *) dari non-koma ( [^,]) diikuti oleh koma ( ,).

Pendekatan itu berfungsi sebagai padanan @.*?,, tetapi tidak untuk hal-hal seperti @.*?string, di situlah yang dicari lebih dari satu karakter. Meniadakan karakter itu mudah, tetapi meniadakan string dalam regexps jauh lebih sulit .

Pendekatan yang berbeda adalah dengan pra-proses input Anda untuk mengganti atau menambahkan stringdengan karakter yang sebaliknya tidak terjadi pada input Anda:

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

Jika Anda tidak dapat menjamin bahwa input tidak akan mengandung karakter pengganti Anda (di \1atas), salah satu pendekatan adalah menggunakan mekanisme melarikan diri:

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")

Itu bekerja untuk strings tetap tetapi tidak untuk regexps sewenang-wenang seperti untuk setara @.*?foo.bar.


Terima kasih banyak atas tanggapan yang baik. Dalam pengeditan saya, saya bertanya contoh lain (lihat edit saya).
nowy1

6

Sudah ada beberapa jawaban yang baik untuk mengatasi awkketidakmampuan melakukan pertandingan yang tidak serakah, jadi saya memberikan beberapa informasi tentang cara alternatif untuk melakukannya menggunakan Perl Compatible Regular Expressions (PCRE). Perhatikan bahwa awkskrip "cocok dan cetak" yang paling sederhana dapat dengan mudah diimplementasikan kembali dengan perlmenggunakan -nopsi baris perintah, dan skrip yang lebih kompleks dapat dikonversi dengan penerjemah Awk to Perl a2p .

Perl memiliki operator non-serakah yang dapat digunakan dalam skrip Perl dan apa pun yang menggunakan PCRE. Misalnya, juga diterapkan dalam -Popsi GNU grep .

PCRE tidak identik dengan ekspresi reguler Perl, tetapi sangat dekat. Ini adalah pilihan populer dari perpustakaan ekspresi reguler untuk banyak program, karena sangat cepat, dan peningkatan Perl untuk ekspresi reguler yang diperluas sangat berguna.

Dari halaman manual 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

Ini adalah pos lama, tetapi informasi berikut mungkin bermanfaat bagi orang lain.

Ada cara, diakui kasar, untuk melakukan pencocokan RE serakah dalam awk. Ide dasarnya adalah menggunakan fungsi match (string, RE), dan secara progresif mengurangi ukuran string sampai match gagal, kira-kira seperti (belum teruji):

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

Untuk ekspresi umum, ini dapat digunakan sebagai pasangan yang tidak serakah:

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
}

Saya menggunakan ini berdasarkan jawaban @ JimMellander. smatchberperilaku seperti match, kembali:

posisi di s mana ekspresi reguler rterjadi, atau 0 jika tidak. Variabel RSTARTdan RLENGTHdiatur ke posisi dan panjang dari string yang cocok.


1

Tidak ada cara di awk untuk melakukan pencocokan non-serakah. Anda mungkin bisa mendapatkan output yang diinginkan. Saran sch akan bekerja untuk baris itu. Jika Anda tidak dapat mengandalkan koma, tetapi "Penulis" selalu merupakan awal dari apa yang Anda inginkan, Anda bisa melakukan ini:

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

Jika jumlah karakter sebelum Penulis selalu sama, Anda bisa melakukan ini:

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

Anda hanya perlu tahu seperti apa data Anda di seluruh rangkaian.


0

Selalu ada cara. Masalah yang diberikan dapat diselesaikan dengan cukup mudah dengan menggunakan koma sebagai pemisah.

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

Ketika jumlah bidang bervariasi sesuatu yang sedikit lebih baik biasanya dibutuhkan. Dalam kasus seperti itu, menemukan kata-kata penghenti sering terbayar, karena Anda dapat memotong apa pun dari garis dengan menggunakannya. Dalam konteks contoh inilah yang saya maksud dengan kata-kata berhenti.

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

0

Saya tahu ini adalah pos lama. Tapi di sini ada sesuatu yang hanya menggunakan awk sebagai OP seperti yang diminta:
A = @ article {gjn2010jucs, Penulis = {Grzegorz J. Nalepa},
gema $ A | sub awk '(/ @ [^,] * /, "")'

Keluaran
:, Penulis = {Grzegorz J. Nalepa},


1
Jawaban itu salah karena sekitar lima alasan.
Scott

3
Bisakah Anda membantu saya memahami apa yang salah? Outputnya tampaknya konsisten dengan apa yang diminta. Berusaha memahami mengapa jawabannya benar / tidak benar.
VINAY NAIR
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.