NON GNU awk ile değişiklikleri yerinde kaydedin


9

Ben OP düzenlemek ve girdi_dosya (lar) içine işlemi kaydetmek zorunda (SO kendisi üzerinde) bir soru rastladım.

Aşağıdakileri yapabileceğimiz tek bir Input_file için biliyorum:

awk '{print "test here..new line for saving.."}' Input_file > temp && mv temp Input_file

Şimdi, aynı tür dosya biçimlerinde değişiklik yapmamız gerektiğini varsayalım (burada .txt olduğunu varsayalım).

Ne denedim / bu sorun için düşündüm: Onun yaklaşımı .txt dosyaları bir for döngüsü geçiyor ve tek çağırmakawkacı verici ve tavsiye DEĞİL bir işlemdir, çünkü gereksiz CPU döngüleri israf edecek ve daha fazla sayıda dosya için daha fazla olurdu yavaş.

Burada, awkyerinde seçeneği desteklemeyen bir NON GNU ile birden çok dosya için yerinde düzenleme yapmak için burada ne yapılabilir . Ben de bu iş parçacığından geçtim Değişiklikleri awk ile kaydedin ama GNON awkolmayan bir awk inplaceseçeneğine sahip olmayacağından GNON awk yardımcısı ve kendi içinde birden fazla dosyayı değiştirmek için pek bir şey yok .

Not: nedenbashetiketekliyorum, benim yanıt bölümünde, geçici dosyaları gerçek Input_file adlarını yeniden eklemek için bash komutları kullandım, böylece ekleyerek.



DÜZENLEME: Ed sir'in yorumuna göre, burada örneklerin bir örneğini ekleyerek, bu iş parçacığının kodunun amacı, genel amaçlı yerinde düzenlemede de kullanılabilir.

Örnek Girdi_dosyaları:

cat test1.txt
onetwo three
tets testtest

cat test2.txt
onetwo three
tets testtest

cat test3.txt
onetwo three
tets testtest

Beklenen çıktı örneği:

cat test1.txt
1
2

cat test2.txt
1
2

cat test3.txt
1
2

1
İlginç ve ilgili awk sorunu ++
anubhava

1
@ RavinderSingh13 Bunu uygulamak için bir sürü dosyanız varsa, neden tek bir çağrıyı awk(belki bir alt kabukta) veya {...}kapalı bir grubu kullanmıyorsunuz ve sonuçları istenen çıktı dosyasına (her girdi dosyası için, veya tüm girdi dosyaları için birleştirilmiş bir dosya). Sonra alt kabuk veya küme ayracı kapalı grubun çıktısını yazılmakta olan geçerli dosyaya yönlendiriyor musunuz? Sadece awkkomutu takip giriş dosyaları bir dize dahil sırayla tüm dosyaları (veya benzer bir şey) işleyecek ??
David C. Rankin

@ DavidC.Rankin, buna cevap verdiğiniz için teşekkür ederim. Evet, siz de benzer bir şey söylediniz efendim, cevabım da bu soruya gönderildi, aynı efendim hakkındaki görüşlerinizi biliyorum.
RavinderSingh13

1
Biraz uyuduktan ve düşündükten sonra awk {..} file1 .. fileX, değiştirilmiş dosyayı şöyle yazarak 2 seçenek (1) görüyorum temp01ve bir sonraki dosyayı işlerken sonraki yinelemede mv -f tmp01 input01, değiştirilen verilerle giriş dosyasının üzerine yazmak için a kullanın; veya (2) komut dosyasının ./tmp/tmp01 ... ./tmp/tmp0Xyürütülmesi sırasında yeni bir dizin yazmanız ve dizindeki awkdosyalar üzerinde bir döngü ile izlemeniz ./tmpve örn. mv -f "$i" "input_${i##*[^0-9]}"(veya eski giriş dosyalarını değiştirmeniz için hangi genişleme yapmanız gerekiyorsa)
David C. Rankin

@ DavidC.Rankin, Görüşlerinizi burada bildirdiğiniz için teşekkür ederiz efendim, IMHO 1. seçeneği biraz riskli olabilir, çünkü awktam kod tamamlanmadan bir şey yapıyoruz , 2. seçenek önerimde kullandığımın hemen hemen aynı olacak, Bu çözüm hakkında düşüncelerinizi bildirirseniz minnettar olun efendim.
RavinderSingh13

Yanıtlar:


6

Bu iş parçacığının ana amacı nasıl NON GNU SAVE yapmak için olduğundan, awkilk önce herhangi bir gereksinimi olan herkese yardımcı olacak şablonunu yayınlıyorum, onlar kendi ana BLOK onların kodunu tutarak kod eklemek / eklemek BEGINve ENDbölüm gerekir gereksinim ve o zaman yerinde düzenleme yapmalıdır:

NOT: Aşağıdaki tüm çıktılarını output_file'a yazacaktır, bu nedenle standart çıktıya herhangi bir şey yazdırmak istiyorsanız, lütfen sadeceaşağıdakileriprint...içermeyen deyimekleyin> (out).

Genel Şablon:

awk -v out_file="out" '
FNR==1{
close(out)
out=out_file count++
rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
    .....your main block code.....
}
END{
 if(rename){
   system(rename)
 }
}
' *.txt


Özel sağlanan numunenin çözümü:

awkKendi içinde aşağıdaki yaklaşım ile geldi (ekledi örnekleri için aşağıdaki benim bu sorunu çözmek ve Input_file kendisi kaydetmek için yaklaşımdır)

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print FNR > (out)
}
END{
  if(rename){
    system(rename)
  }
}
' *.txt

NOT: Bu sadece düzenlenmiş çıktıyı Input_file (lar) içine kaydetmek için bir testtir, biri kendi BEGIN bölümünü, programlarındaki END bölümü ile birlikte kullanabilir, ana bölüm özel sorunun kendisinin gereksinimine göre olmalıdır.

Adil uyarı: Ayrıca bu yaklaşım yolda yeni bir geçici çıkış dosyası oluşturduğundan, sistemler üzerinde yeterli alanımız olduğundan emin olun, ancak nihai sonuçta bu yalnızca ana Girdi_dosyalarını koruyacak, ancak işlemler sırasında sistem / dizin üzerinde alan gerektirecektir.



Yukarıdaki kod için bir test aşağıdadır.

Bir örnek ile programın Yürütme: Aşağıdaki varsayalım olan.txtinput_file (ler):

cat << EOF > test1.txt
onetwo three
tets testtest
EOF

cat << EOF > test2.txt
onetwo three
tets testtest
EOF

cat << EOF > test3.txt
onetwo three
tets testtest
EOF

Şimdi aşağıdaki kodu çalıştırdığımızda:

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print "new_lines_here...." > (out)
}
END{
  if(rename){
    system("ls -lhtr;" rename)
  }
}
' *.txt

NOT: Ben yere sahipls -lhtrdesystemdaha sonra bunların gerçek adı içine yeniden adlandırır çünkü kasten o (geçici temelini) oluşturarak hangi çıktı dosyaları görmek için bölüm.

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out2
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out1
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out0

Biz yaptığımızda ls -lhtrsonra awk komut çalıştıran ile yapılır, sadece görebiliyordu .txtorada dosyaları.

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt


Açıklama: Buraya yukarıdaki komutun ayrıntılı bir açıklamasını ekleme:

awk -v out_file="out" '                                    ##Starting awk program from here, creating a variable named out_file whose value SHOULD BE a name of files which are NOT present in our current directory. Basically by this name temporary files will be created which will be later renamed to actual files.
FNR==1{                                                    ##Checking condition if this is very first line of current Input_file then do following.
  close(out)                                               ##Using close function of awk here, because we are putting output to temp files and then renaming them so making sure that we shouldn't get too many files opened error by CLOSING it.
  out=out_file count++                                     ##Creating out variable here, whose value is value of variable out_file(defined in awk -v section) then variable count whose value will be keep increment with 1 whenever cursor comes here.
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"     ##Creating a variable named rename, whose work is to execute commands(rename ones) once we are done with processing all the Input_file(s), this will be executed in END section.
}                                                          ##Closing BLOCK for FNR==1  condition here.
{                                                          ##Starting main BLOCK from here.
  print "new_lines_here...." > (out)                       ##Doing printing in this example to out file.
}                                                          ##Closing main BLOCK here.
END{                                                       ##Starting END block for this specific program here.
  if(rename){                                              ##Checking condition if rename variable is NOT NULL then do following.
    system(rename)                                         ##Using system command and placing renme variable inside which will actually execute mv commands to rename files from out01 etc to Input_file etc.
  }
}                                                          ##Closing END block of this program here.
' *.txt                                                    ##Mentioning Input_file(s) with their extensions here.

1
İlginç gerçek: Giriş dosyasını FNR==1blok halinde silerseniz, değişiklikleri yerinde kaydedebilirsiniz. Gibi awk 'FNR==1{system("rm " FILENAME)} {print "new lines" > FILENAME}' files.... Bu hiç güvenilir değil (tam veri kaybının olması muhtemeldir), ancak yine de çoğunlukla iyi çalışır: D
oguz ismail

1
Çok iyi açıklanmış geçici çözüm
anubhava

3

Bunu yapmaya çalışacak olsaydım muhtemelen böyle bir şeyle giderdim:

$ cat ../tst.awk
FNR==1 { saveChanges() }
{ print FNR > new }
END { saveChanges() }

function saveChanges(   bak, result, mkBackup, overwriteOrig, rmBackup) {
    if ( new != "" ) {
        bak = old ".bak"
        mkBackup = "cp \047" old "\047 \047" bak "\047; echo \"$?\""
        if ( (mkBackup | getline result) > 0 ) {
            if (result == 0) {
                overwriteOrig = "mv \047" new "\047 \047" old "\047; echo \"$?\""
                if ( (overwriteOrig | getline result) > 0 ) {
                    if (result == 0) {
                        rmBackup = "rm -f \047" bak "\047"
                        system(rmBackup)
                    }
                }
            }
        }
        close(rmBackup)
        close(overwriteOrig)
        close(mkBackup)
    }
    old = FILENAME
    new = FILENAME ".new"
}

$ awk -f ../tst.awk test1.txt test2.txt test3.txt

İlk önce özgün dosyayı yedek kopyalamak ve daha sonra orijinal kaydetme değişiklikleri üzerinde çalışmayı tercih ederdim ama bunu yapmak istenmeyen her girdi dosyası için FILENAME değişkeninin değerini değiştirir.

Dizininizde whatever.bakveya whatever.newdizininizde orijinal bir dosya varsa, bunların üzerine geçici dosyalar yazacağınızı ve bunun için de bir test eklemeniz gerektiğini unutmayın. mktempGeçici dosya adlarını almak için yapılan bir çağrı daha sağlam olur.

Bu durumda FAR daha kullanışlı bir şey, başka bir komutu yürüten ve "inplace" düzenleme bölümünü yapan bir araç olacaktır, çünkü POSIX sed, awk, grep, tr, her neyse ve print > outher değer yazdırmak istediğinizde komut dosyanızın sözdizimini vb. olarak değiştirmenizi gerektirmez . Basit, kırılgan bir örnek:

$ cat inedit
#!/bin/env bash

for (( pos=$#; pos>1; pos-- )); do
    if [[ -f "${!pos}" ]]; then
        filesStartPos="$pos"
    else
        break
    fi
done

files=()
cmd=()
for (( pos=1; pos<=$#; pos++)); do
    arg="${!pos}"
    if (( pos < filesStartPos )); then
        cmd+=( "$arg" )
    else
        files+=( "$arg" )
    fi
done

tmp=$(mktemp)
trap 'rm -f "$tmp"; exit' 0

for file in "${files[@]}"; do
    "${cmd[@]}" "$file" > "$tmp" && mv -- "$tmp" "$file"
done

aşağıdaki gibi kullanabilirsiniz:

$ awk '{print FNR}' test1.txt test2.txt test3.txt
1
2
1
2
1
2

$ ./inedit awk '{print FNR}' test1.txt test2.txt test3.txt

$ tail test1.txt test2.txt test3.txt
==> test1.txt <==
1
2

==> test2.txt <==
1
2

==> test3.txt <==
1
2

Bu ineditkomut dosyasıyla ilgili bariz bir sorun , birden çok giriş dosyanız olduğunda giriş / çıkış dosyalarını komuttan ayrı olarak tanımlamanın güçlüğüdür. Yukarıdaki komut dosyası, tüm giriş dosyalarının komutun sonunda bir liste olarak göründüğünü ve komutun her seferinde birer birer çalıştırıldığını varsayar; ancak bu, dosyayı 2 veya daha fazla dosya gerektiren komut dosyaları için kullanamayacağınız anlamına gelir. bir zaman, örneğin:

awk 'NR==FNR{a[$1];next} $1 in a' file1 file2

veya arg listesindeki dosyalar arasında değişkenler ayarlayan komut dosyaları, örneğin:

awk '{print $7}' FS=',' file1 FS=':' file2

Okuyucunun egzersizi olarak daha sağlam kalmasını sağlamak, ancak xargssinopsisin, sağlam bir ineditçalışmanın nasıl olması gerektiğine dair bir başlangıç ​​noktası olarak görünmesi :-).


0

Kabuk çözümü basit ve muhtemelen yeterince hızlı:

for f in *.txt
do  awk '...' $f > $f.tmp
    mv $f.tmp $f
done

Farklı bir çözüm için sadece bunun çok yavaş olduğunu kesin olarak gösterdiyseniz. Unutmayın: erken optimizasyon tüm kötülüklerin köküdür.


Cevabınız için teşekkür ederim, ancak sorumun kendisinde de belirtildiği gibi, bu cevabın farkındayız, ancak bu gerçekten bu görevi yerine getirmenin aşırı yükü, bu yüzden awk içinde bir şey deneyebilirsek bahsetmiştim. Zaman ayırdığınız için teşekkürler ve cevap verin.
RavinderSingh13
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.