AWK'da düzenli bir ifadenin açgözlülüğü nasıl azaltılır?


14

Açgözlü olmayan desen (düzenli ifade) eşleştirme yapmak istiyorum awk. İşte bir örnek:

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

Kısa dizeyi seçen normal bir ifade yazmak mümkün müdür?

@article{gjn,

Bu uzun dize yerine ?:

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

Bu sonucu almak istiyorum:

 Author =   {Grzegorz J. Nalepa},



Başka bir örneğim var:

echo " , makale {gjn, Yazar = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); Yazdır }'
      ↑ ↑ ^ ^ ^ ^ ^

Ben değiştiğini Not @virgül (karakterler ,girdi dizesi ve düzenli ifade hem birinci pozisyonda) karakterler (ve de değişti .*için [^,]*). Kısa dizeyi seçen normal bir ifade yazmak mümkün müdür?

, Author =   {Grzegorz J. Nalepa},

uzun dize yerine ?:

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

Bu sonucu almak istiyorum:

,article{gjn

4
Regex'in güçlü HTML ayrıştırma için yetersiz olması gibi, muhtemelen bu tür bağlama duyarlı dilbilgisi ayrıştırmalarını yapamayacaklardır. Bununla birlikte, girdi kümeniz oldukça kısıtlanmış ve iyi biçimlendirilmişse, kısıtlamalarınızın ne olduğunu beyan ettiğiniz sürece normal ifade ile kurtulabilirsiniz. Örneğin için görünebilir AuthorArkasında boşluk virgül ve boşluk aşağıdaki ardından =ardından boşluk ardından {herhangi olmayan izledi }izledi }olduğunu sen iç içe olamazlar bu (diğer şeylerin yanı sıra) gerektirmesine rağmen, {}= { ... }kısmı.
jw013

@ jw013, açıklaman için teşekkürler. Yine de diğer kullanıcıların önerilerini bekleyeceğim.
nowy1

Yanıtlar:


18

Seçmek @ve ondan sonraki ilke kadar yapmak ,istiyorsanız,@[^,]*,

Bunu virgül olmayan @bir sayı ( *) ve [^,]ardından virgül ( ) takip eder ,.

Bu yaklaşım , şu @.*?,gibi şeylerin eşdeğeri gibi çalışır , ancak @.*?stringpeşinde olan tek bir karakterden daha fazlasıdır. Bir karakteri reddetmek kolaydır, ancak normal ifadelerde dizeleri reddetmek çok daha zordur .

Farklı bir yaklaşım, girişinizde stringaksi belirtilmedikçe bir karakterin yerine veya önüne bir giriş koymak için girişinizi önceden işlemektir :

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

Girdinin yerine koyma karakterinizi içermeyeceğini garanti edemiyorsanız ( \1yukarıda), yaklaşımlardan biri kaçan bir mekanizma kullanmaktır:

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

Bu sabit strings için işe yarıyor ama eşdeğeri gibi keyfi regexps için değil @.*?foo.bar.


İyi yanıt için çok teşekkür ederim. Düzenlememde başka bir örnek daha sordum (düzenlememe bakın).
nowy1

6

awkAçgözlü olmayan maçlar yapamaması için geçici çözümler sağlayan birkaç iyi yanıt var , bu yüzden Perl Uyumlu Düzenli İfadeler (PCRE) kullanarak bunu yapmanın alternatif bir yolu hakkında bazı bilgiler sağlıyorum . En basit "eşleştirme ve yazdırma" awkkomut dosyalarının komut satırı seçeneği perlkullanılarak kolayca yeniden uygulanabileceğini -nve a2p Awk - Perl çevirmeniyle daha karmaşık komut dosyalarının dönüştürülebileceğini unutmayın .

Perl , Perl betiklerinde ve PCRE kullanan her şeyde kullanılabilecek açgözlü olmayan bir operatöre sahiptir. Örneğin, GNU grep'in -Pseçeneğinde de uygulanır .

PCRE, Perl'in düzenli ifadeleri ile aynı değildir , ancak çok yakındır. Birçok program için popüler bir ifade kütüphanesi popüler bir seçimdir, çünkü çok hızlıdır ve genişletilmiş düzenli ifadelere Perl geliştirmeleri çok yararlıdır.

Gönderen perlre (1) kılavuz sayfasında:

   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

Bu eski bir gönderi, ancak aşağıdaki bilgiler başkaları için yararlı olabilir.

Kuşkusuz kaba, açgözlü olmayan açgözlü RE eşleştirme yapmak için bir yolu vardır. Temel fikir, match (string, RE) işlevini kullanmak ve eşleşme başarısız olana kadar (test edilmemiş) gibi dizenin boyutunu aşamalı olarak azaltmaktır:

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

Genel ifadeler için bu, açgözlü olmayan bir eşleşme olarak kullanılabilir:

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
}

Bunu @ JimMellander'ın cevabına dayanarak kullanıyorum. smatchaşağıdaki gibi davranır match:

s normal ifadenin rgerçekleştiği konum veya gerçekleşmezse 0. Değişkenler RSTARTve RLENGTHeşleşen dizisinin pozisyonuna ve uzunluğu ayarlanır.


1

Awk'ta açgözlü olmayan eşleştirme yapmanın bir yolu yoktur. Yine de, istenen çıktıyı alabilirsiniz. sch'un önerisi bu satır için işe yarayacaktır. Bir virgüle güvenemezseniz, ancak "Yazar" her zaman istediğiniz şeyin başlangıcıysa, bunu yapabilirsiniz:

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

Yazardan önceki karakter sayısı her zaman aynı ise, bunu yapabilirsiniz:

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

Tüm verilerin genelinde verilerinizin nasıl göründüğünü bilmeniz yeterlidir.


0

Her zaman bir yolu var. Verilen problem ayırıcı olarak virgül kullanılarak kolayca çözülebilir.

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

Alan sayısı değiştiğinde, genellikle biraz daha iyi bir şeye ihtiyaç duyulur. Böyle bir durumda bir durak kelime bulmak genellikle işe yarar, çünkü bunları kullanarak satırdan herhangi bir şeyi kesebilirsiniz. Örnek bağlamında dur sözcükleriyle kastettiğim şu.

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

0

Bunun eski bir yazı olduğunu biliyorum. Ancak burada
awk'yi OP olarak istendiği gibi kullanan bir şey var: A = @ article {gjn2010jucs, Author = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Çıktı:,
Yazar = {Grzegorz J. Nalepa},


1
Bu cevap yaklaşık beş nedenden dolayı yanlış.
Scott

3
Neyin yanlış olduğunu anlamama yardım eder misiniz? Çıktı talep edilenle tutarlı görünüyor. Cevabın neden doğru / doğru olmadığını anlamaya çalışmak.
VINAY NAIR
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.