Simpan modifikasi di tempat dengan NON GNU awk

9

Saya telah menemukan pertanyaan (pada SO itu sendiri) di mana OP harus melakukan edit dan menyimpan operasi ke Input_file itu sendiri.

Saya tahu untuk satu Input_file yang bisa kami lakukan sebagai berikut:

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

Sekarang katakanlah kita perlu membuat perubahan dalam format file yang sama (asumsikan .txt di sini).

Apa yang saya coba / pikirkan untuk masalah ini: Pendekatannya adalah melalui loop untuk file .txt dan memanggil tunggalawkadalah proses yang menyakitkan dan TIDAK direkomendasikan, karena akan membuang siklus cpu yang tidak perlu dan untuk lebih banyak jumlah file akan lebih lambat.

Jadi apa yang mungkin bisa dilakukan di sini untuk melakukan inplace edit untuk banyak file dengan NON GNU awkyang tidak mendukung opsi inplace. Saya juga telah melalui utas ini. Simpan modifikasi di tempat dengan awk tetapi tidak ada banyak untuk wakil NON GNU awk dan mengubah beberapa file di dalamawk dirinya sendiri, karena awk non GNU tidak akan memiliki inplacepilihan untuk itu.

CATATAN: Mengapa saya menambahkanbashtag sejak itu, di bagian jawaban saya, saya telah menggunakan perintah bash untuk mengganti nama file sementara ke nama Input_file mereka yang sebenarnya sehingga menambahkannya.



EDIT: Sesuai komentar Ed sir menambahkan contoh sampel di sini, meskipun tujuan dari kode utas ini dapat digunakan oleh mengedit inplace tujuan umum juga.

Sampel Input_file:

cat test1.txt
onetwo three
tets testtest

cat test2.txt
onetwo three
tets testtest

cat test3.txt
onetwo three
tets testtest

Contoh output yang diharapkan:

cat test1.txt
1
2

cat test2.txt
1
2

cat test3.txt
1
2
RavinderSingh13
sumber
1
Masalah awk yang menarik dan berkaitan ++
anubhava
1
@ RavinderSingh13 jika Anda memiliki sejumlah besar file untuk menerapkan ini, mengapa tidak menggunakan satu panggilan untuk awk, (mungkin dalam subkulit) atau {...}grup terlampir dan kemudian menulis hasilnya ke file output yang diinginkan (baik untuk setiap file input, atau file gabungan untuk semua file input). Kemudian Anda cukup mengarahkan output dari subkulit atau kelompok kurung kurawal ke file saat ini yang sedang ditulis? Cukup dengan memasukkan serangkaian file input setelah awkperintah akan memproses semua file secara berurutan (atau yang serupa) ??
David C. Rankin
@ DavidC.Rankin, terima kasih telah membalas yang ini. Ya saya telah memposting hal serupa yang Anda katakan pak, jawaban saya juga diposting dalam pertanyaan ini, biar tahu pandangan Anda tentang tuan yang sama, tepuk tangan.
RavinderSingh13
1
Setelah beberapa tidur dan memikirkannya, saya melihat 2 opsi (1) dengan awk {..} file1 .. fileXmenulis file yang dimodifikasi sebagai, misalnya temp01dan dalam iterasi Anda berikutnya saat memproses file berikutnya, gunakan a mv -f tmp01 input01untuk menimpa file input dengan data yang dimodifikasi; atau (2) cukup menulis direktori baru ./tmp/tmp01 ... ./tmp/tmp0Xselama eksekusi awkskrip dan menindaklanjutinya dengan loop atas file dalam ./tmpdirektori dan, misalnya mv -f "$i" "input_${i##*[^0-9]}"(atau ekspansi apa pun yang Anda perlukan untuk mengganti file input lama.
David C. Rankin
@ DavidC.Rankin, Terima kasih telah membiarkan pandangan Anda tahu di sini, Pak, opsi IMHO 1 mungkin sedikit berisiko, karena kita sedang melakukan sesuatu tanpa awkpenyelesaian kode lengkap, opsi ke-2 hampir sama dengan apa yang saya gunakan dalam saran saya, akan bersyukurlah jika Anda bisa memberi tahu pikiran Anda tentang solusi itu, Pak.
RavinderSingh13

Jawaban:

6

Karena tujuan utama dari utas ini adalah bagaimana melakukan inplace SIMPAN di NON GNU awkjadi saya memposting templatnya terlebih dahulu yang akan membantu siapa pun dalam segala jenis persyaratan, mereka perlu menambahkan / menambahkan BEGINdan ENDbagian dalam kode mereka menjaga BLOCK utama mereka sesuai persyaratan dan harus melakukan inplace edit kemudian:

CATATAN: Mengikuti akan menulis semua outputnya ke output_file, jadi jika Anda ingin mencetak apa pun ke output standar, harap hanya menambahkanprint...pernyataan tanpa> (out)mengikuti.

Templat Generik:

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


Solusi sampel khusus yang disediakan:

Saya telah datang dengan pendekatan berikut dalam awkdirinya sendiri (untuk sampel tambahan berikut adalah pendekatan saya untuk menyelesaikan ini dan menyimpan output ke Input_file itu sendiri)

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

CATATAN: ini hanya tes untuk menyimpan output yang diedit ke dalam Input_file itu sendiri, orang dapat menggunakan bagian BEGIN-nya, bersama dengan bagian END-nya dalam program mereka, bagian utama harus sesuai dengan persyaratan pertanyaan spesifik itu sendiri.

Peringatan yang adil: Juga karena pendekatan ini membuat file sementara sementara baru di jalur jadi lebih baik pastikan kita memiliki cukup ruang pada sistem, meskipun pada hasil akhir ini hanya akan menyimpan Input_file utama tetapi selama operasi itu membutuhkan ruang pada sistem / direktori



Berikut ini adalah tes untuk kode di atas.

Eksekusi program dengan contoh: Mari kita asumsikan berikut ini adalah.txtInput_file:

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

Sekarang ketika kita menjalankan kode berikut:

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

CATATAN: Saya memiliki tempatls -lhtrdisystembagian yang sengaja untuk melihat file output mana yang sedang dibuat (sementara) karena nanti akan mengubah nama mereka menjadi nama sebenarnya.

-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

Ketika kita melakukan skrip ls -lhtrsetelah awkselesai menjalankan, kita hanya bisa melihat .txtfile di sana.

-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


Penjelasan: Menambahkan penjelasan rinci tentang perintah di atas di sini:

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.
RavinderSingh13
sumber
1
Fakta menyenangkan: jika Anda menghapus file input dalam FNR==1blokir, Anda masih dapat menyimpan perubahan di tempat. Seperti awk 'FNR==1{system("rm " FILENAME)} {print "new lines" > FILENAME}' files.... Ini sama sekali tidak dapat diandalkan (kehilangan data lengkap kemungkinan akan terjadi), tetapi tetap saja, sebagian besar berfungsi dengan baik: D
oguz ismail
1
Bekerja dengan sangat baik di sekitar
anubhava
3

Saya mungkin akan pergi dengan sesuatu seperti ini jika saya mencoba melakukan ini:

$ 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

Saya lebih suka menyalin file asli ke cadangan terlebih dahulu dan kemudian beroperasi pada perubahan penyimpanan ke yang asli tetapi melakukan itu akan mengubah nilai variabel FILENAME untuk setiap file input yang tidak diinginkan.

Perhatikan bahwa jika Anda memiliki file asli bernama whatever.bakatau whatever.newdi direktori Anda maka Anda akan menimpanya dengan file temp sehingga Anda perlu menambahkan tes untuk itu juga. Panggilan kemktemp mendapatkan nama file temp akan lebih kuat.

JAUH lebih berguna untuk dimiliki dalam situasi ini akan menjadi alat yang mengeksekusi perintah lain dan melakukan bagian pengeditan "inplace" karena itu dapat digunakan untuk menyediakan pengeditan "inplace" untuk POSIX, awk, grep, tr, apa pun dan tidak akan mengharuskan Anda untuk mengubah sintaks skrip Anda ke print > outdll. setiap kali Anda ingin mencetak nilai. Contoh sederhana, rapuh:

$ 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

yang akan Anda gunakan sebagai berikut:

$ 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

Satu masalah yang jelas dengan ineditskrip itu adalah kesulitan mengidentifikasi file input / output secara terpisah dari perintah ketika Anda memiliki banyak file input. Script di atas mengasumsikan semua file input muncul sebagai daftar di akhir perintah dan perintah dijalankan terhadap mereka satu per satu, tetapi tentu saja itu berarti Anda tidak dapat menggunakannya untuk skrip yang memerlukan 2 file atau lebih pada suatu waktu, misalnya:

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

atau skrip yang mengatur variabel antara file dalam daftar arg, mis:

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

Menjadikannya lebih tangguh dibiarkan sebagai latihan untuk pembaca tetapi melihat ke xargssinopsis sebagai titik awal untuk bagaimana yang kuat ineditharus bekerja :-).

Ed Morton
sumber
0

Solusi shell sederhana dan kemungkinan cukup cepat:

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

Hanya mencari solusi yang berbeda jika Anda secara meyakinkan menunjukkan bahwa ini terlalu lambat. Ingat: optimasi prematur adalah akar dari semua kejahatan.

pengguna448810
sumber
Terima kasih atas balasan Anda, tetapi seperti yang disebutkan dalam pertanyaan saya sendiri, kami menyadari jawaban ini, tetapi ini benar-benar kerja keras untuk melakukan tugas ini, itu sebabnya saya menyebutkan jika kami dapat mencoba sesuatu dalam awk itu sendiri. Terima kasih atas waktu dan jawabannya di sini.
RavinderSingh13