Regex dizeyi değiştirmek için AWK ile nasıl kullanılır?


13

Bir dosyadan bazı metinler olduğunu varsayalım:

(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 23" "#2")
("Exercises 31" "#30")
("Notes and References 42" "#34"))
)

Her sayıya 11 eklemek ve ardından "her satırda bir tane eklemek istiyorum , yani

(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 34" "#13")
("Exercises 42" "#41")
("Notes and References 53" "#45"))
)

İşte benim çözüm GNU AWK ve regex kullanarak:

awk -F'#' 'NF>1{gsub(/"(\d+)\""/, "\1+11\"")}'

yani, grubun temsil ettiği yer (\d+)\"ile değiştirmek istiyorum . Ama işe yaramıyor. Nasıl çalıştırabilirim?\1+10\"\1(\d+)

Gawk en iyi çözüm değilse, başka ne kullanılabilir?


Çoğaltma için üzgünüm. Ama önce stackoverflow'u sordum ve tatmin edici bir cevap almadım, bu yüzden göç için işaretledim. Ama bir süre olmadı, bu yüzden olmasını beklemiyordum ve sonra Unix.SE'ye sordum.
Tim

Yanıtlar:


12

Bunu deneyin (gawk gereklidir).

awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}' YourFile

Örneğinizle test edin:

kent$  echo '(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 2" "#2")
("Exercises 30" "#30")
("Notes and References 34" "#34"))
)
'|awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}'   
(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 13" "#13")
("Exercises 41" "#41")
("Notes and References 45" "#45"))
)

İki sayı (örn. 1 "ve" # 1 ") farklıysa veya bu desenle aynı satırda daha fazla sayı varsa (örneğin 23" ... 32 "..." # bu komutun çalışmayacağını unutmayın. 123 ").


GÜNCELLEME

@Tim (OP) ", aynı satırda izlenen sayının farklı olabileceğini söylediğinden, önceki çözümümde bazı değişiklikler yaptım ve yeni örneğiniz için çalışmasını sağladım .

BTW, örnekten bir içerik yapısı tablosu olabileceğini hissediyorum, bu yüzden iki sayının nasıl farklı olabileceğini göremiyorum. Birincisi, yazdırılan sayfa numarası, ikincisi # ile sayfa dizini olacaktır. Haklı mıyım?

Neyse, ihtiyacınızı en iyi biliyorsunuz. Şimdi yeni çözüm, hala gawk ile (okumayı kolaylaştırmak için komutu satırlara ayırıyorum):

awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;}
        a=gensub(/.* ([0-9]+)$/,"\\1","g",$1);
        b=gensub(/([0-9]+)\"/,"\\1","g",$2); 
        gsub(/[0-9]+$/,a+11,$1);
        gsub(/^[0-9]+/,b+11,$2);
        print $1,$2
}' yourFile

yeni örneğinizle test edin :

kent$  echo '(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 23" "#2")
("Exercises 31" "#30")
("Notes and References 42" "#34"))
)
'|awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;}
        a=gensub(/.* ([0-9]+)$/,"\\1","g",$1);
        b=gensub(/([0-9]+)\"/,"\\1","g",$2); 
        gsub(/[0-9]+$/,a+11,$1);
        gsub(/^[0-9]+/,b+11,$2);
        print $1,$2
}'                        
(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 34" "#13")
("Exercises 42" "#41")
("Notes and References 53" "#45"))
)


EDIT2 @Tim adlı kullanıcının yorumuna dayanmaktadır

(1) FS = OFS = "\" \ "#" hem girdi hem de çıktıdaki alan ayırıcısının çift tırnak, boşluk, çift tırnak ve # olduğu anlamına mı geliyor? Neden çift tırnak iki kez belirtilsin?

Hem giriş hem de çıkış bölümünde ayırıcı için haklısınız. Ayırıcıyı şu şekilde tanımladı:

" "#

İki çift tırnak vardır, çünkü istediğiniz iki sayıyı yakalamak daha kolaydır (örnek girişinize göre).

(2) /.* ([0-9] +) $ / 'da, $ dizenin sonu anlamına mı geliyor?

Kesinlikle!

(3) gensub () 'un üçüncü argümanında "g" ve "G" arasındaki fark nedir? G ve g arasında fark yoktur. Şuna bir bak:

gensub(regexp, replacement, how [, target]) #
    Search the target string target for matches of the regular expression regexp. 
    If "how" is a string beginning with g or G (short for global”), then 
        replace all matches of regexp with replacement.

Bu, http://www.gnu.org/s/gawk/manual/html_node/String-Functions.html adresinden . gensub'un detaylı kullanımını öğrenmek için okuyabilirsiniz.


Teşekkürler! 1 "ve" # 1 "gibi iki sayı farklıysa nasıl çalıştıracağını merak ediyorum?
Tim

bu cevap mevcut gereksiniminiz / örneğiniz için geçerlidir. gereksinim değişirse, soruyu düzenleyebilir ve daha iyi bir örnek verebilirsiniz. ve kodunuzdan awk -F'#'sadece '#' işaretinden sonra değişiklik yapmak istediğiniz anlaşılıyor.
Kent

Önerin için teşekkürler. Örneğimi sadece iki sayı aynı olmayacak şekilde değiştirdim.
Tim

@ Yeni örneğiniz için güncellenmiş cevabımı görün.
Kent

Teşekkürler! Bazı sorular: (1) FS=OFS="\" \"#"hem girdi hem de çıktıdaki alan ayırıcısının çift tırnak, boşluk, çift tırnak ve # olduğu anlamına mı geliyor? neden çift tırnak iki kez belirtin? (2) in /.* ([0-9]+)$/, $dizenin sonu anlamına mı geliyor? (3) gensub () üçüncü bağımsız olarak, arasındaki fark ne "g"ve "G"?
Tim

7

Normal ifade ikameleri sağlayan hemen hemen her aracın aksine, awk, \1yedek metin gibi geri başvurulara izin vermez . Eğer kullanırsanız GNU Awk eşleşti gruplara erişim sağlar matchfonksiyonu , ancak birlikte ~veya subveya gsub.

Ayrıca \1, desteklense bile , snippet'inizin +11sayısal bir hesaplama yerine dizeyi ekleyeceğini unutmayın. Ayrıca, regexp'in değil çok doğru, sen eşleştirme gibi şeyler olduğunu "42""ve değil "#42".

İşte bir garip çözüm (uyarı, test edilmemiş). Her hat için yalnızca tek bir değiştirme gerçekleştirir.

awk '
  match($0, /"#[0-9]+"/) {
    n = substr($0, RSTART+2, RLENGTH-3) + 11;
    $0 = substr($0, 1, RSTART+1) n substr($0, RSTART+RLENGTH-1)
  }
  1 {print}'

Perl'de daha basit olurdu.

perl -pe 's/(?<="#)[0-9]+(?=")/$1+11/e'

Cevabınızın ilk cümlesi tam olarak aradığım şeydi. Ancak, "... yedek metinde" demiş olmanız takip eden bir soruyu gündeme getirmektedir: awk, normal ifade modelinin kendisinde geri göndermelere izin veriyor mu?
Wildcard

1
@Wildcard Hayır, awk sadece grupları takip etmez (bahsettiğim GNU uzantısı hariç).
Gilles 'SO- kötü olmayı bırak'

5

awkyapabilir, ancak backreferencing kullanarak bile doğrudan değildir.
GNU awk , gensub şeklinde (kısmi) geri referansa sahiptir .

Örnekleri 123"geçici olarak paketlenir \x01ve \x02değiştirilmemiş (for sub(). Co) olarak işaretlenir

Ya da gittikçe döngü değiştiren adaylar arasında gezinebilirsiniz, bu durumda geri başvuru ve "parantez" gerekmez; ancak karakter indeksini takip etmek gerekir.

awk '{$0=gensub(/([0-9]+)\"/, "\x01\\1\"\x02", "g", $0 )
      while ( match($0, /\x01[0-9]+\"\x02/) ) {
        temp=substr( $0, RSTART, RLENGTH )
        numb=substr( temp, 2, RLENGTH-3 ) + 11
        sub( /\x01[0-9]+\"\x02/, numb "\"" ) 
      } print }'

Burada başka bir yol, gensubve dizi splitve \x01alan ayırıcı ( bölme için ) olarak kullanılır. \ X02 bir dizi öğesini aritmetik toplama için aday olarak işaretler.

awk 'BEGIN{ ORS="" } {
     $0=gensub(/([0-9]+)\"/, "\x01\x02\\1\x01\"", "g", $0 )
     split( $0, a, "\x01" )
     for (i=0; i<length(a); i++) { 
       if( substr(a[i],1,1)=="\x02" ) { a[i]=substr(a[i],2) + 11 }
       print a[i]
     } print "\n" }'

Teşekkürler! İlk kodunuzda, (1) ne anlama "\x01\\1\"\x02"geliyor? Hala anlamıyorum \x01ve \x02. (2) dönüş ne kadar farklı olduğunu $0tarafından gensubve $0son argüman olarak gensub?
Tim

@Tim. Onaltılık değerler \x01ve \x02ikame işaretleri olarak kullanılır. Bu değerler şunlardır derece birinde olması muhtemel Normal Onlar Re .. sadece geçici etiketlerdir .. onlar eşit olacak şekilde "yüksek" güvenli kullanımı (yani. Değil önceden varolan olanlar ile bir çatışma karşılaşmak), metin dosyası $0=gensub(... $0).. Bunu görmek link String-Manipulation İşlevler , ancak özet olarak: (gensub), işlevin sonucu olarak değiştirilen dizeyi döndürür ve orijinal hedef dize değiştirilmez. ... $0=Sadece orijinal hedefi değiştirir ..
Peter.O

3

(G) awk içindeki çözümler oldukça karmaşık göründüğünden, Perl'e alternatif bir çözüm eklemek istedim:

perl -wpe 's/\d+(?=")/$&+11/eg' < in.txt > out.txt

Açıklama:

  • Bu seçenek -wuyarıları etkinleştirir (bu sizi olası istenmeyen etkiler konusunda uyaracaktır).
  • Seçenek -p, sed veya awk'ye benzer şekilde çalışan kodun etrafında, her girdi satırını otomatik olarak varsayılan değişkene kaydederek bir döngü anlamına gelir $_.
  • Option -e, perl'e program kodunun komut dosyasında değil, komut satırında izlendiğini söyler.
  • Kod, bir basamak dizisinin ardından a gelirse, ekte bir sayı olarak yorumlanan dizinin yanı sıra 11 ile değiştirilecek bir normal ifade ikamesidir ( s/.../.../) .$_"
  • Sıfır genişlikli pozitif ileriye bakma iddiası (?=pattern) için görünüyor "biz yerine bunu tekrarlamak zorunda kalmamak için, maçın içine almadan. Değiştirmedeki MATCH değişkeni $&yalnızca sayıyı içerir.
  • /eRegex için değiştirici söyler perldize olarak alarak yerine kod olarak değiştirilmesini "yürütme" için.
  • /gDeğiştirici doğrultusunda her maç onu tekrarlayarak "global" bir yedek yapar.

MATCH değişkeni $&maalesef 5.20'den önceki Perl versiyonlarındaki kod performansına zarar verecektir. Daha hızlı (ve çok daha karmaşık olmayan) bir çözüm, gruplama ve geri başvuruyu $1kullanır:

perl -wpe 's/(\d+)?="/$1+11/eg' < in.txt > out.txt

Eğer ileriye dönük iddia çok kafa karıştırıcı görünüyorsa, tırnak işaretini de açıkça değiştirebilirsiniz:

perl -wpe 's/(\d+)"/$1+11 . q{"}/eg' < in.txt > out.txt
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.