web-dev-qa-db-ja.com

NON GNU awkを使用して変更を適切に保存します

OPが編集およびInput_file(s)自体への保存操作を実行する必要がある質問(SO自体)に遭遇しました。

1つのInput_fileについて、次のことを実行できます。

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

次に、同じ種類のファイル形式(ここでは.txtと仮定)で変更を加える必要があるとしましょう。

この問題に対して私が試した/考えたこと:そのアプローチはforループを通過します。 txtファイルと単一のawkの呼び出しは、不必要なCPUサイクルを浪費し、ファイル数が多くなると遅くなるため、面倒で推奨されないプロセスです。

したがって、NON GNU awkで複数のファイルのインプレース編集を実行するためにここで何ができるか。これは、インプレースオプションをサポートしていません。私もこのスレッドを通過しました 変更をawkで適切に保存 しかし、NON GNU awk viceとawk自体の内部で複数のファイルを変更することは、GNU awkにはinplaceオプションがありません。

注:なぜ私はbashタグを追加するのですか、私の回答パートIにbashコマンドを使用して、一時ファイルの名前を実際のInput_file名に変更して追加しました。



EDIT:このスレッドのコードの目的にもかかわらず、ここにサンプルの例を追加するEd sirのコメントに従って汎用のインプレース編集でも使用できます。

Sample Input_file(s):

cat test1.txt
onetwo three
tets testtest

cat test2.txt
onetwo three
tets testtest

cat test3.txt
onetwo three
tets testtest

予想される出力のサンプル:

cat test1.txt
1
2

cat test2.txt
1
2

cat test3.txt
1
2
9
RavinderSingh13

このスレッドの主な目的は、SAVEをNON GNU awkにインプレースする方法です。そのため、あらゆる種類の要件で誰でも役立つテンプレートを最初に投稿しています。 BEGINおよびENDセクションをコードに追加/追加して、要件に従ってメインBLOCKを維持し、インプレース編集を実行する必要があります。

注:次はすべての出力をoutput_fileに書き込みます。標準出力に何かを印刷したい場合は、追加してください以下の> (out)のないprint...ステートメント。

汎用テンプレート:

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


特定の提供されたサンプルのソリューション:

私はawk自体の中で次のアプローチを考え出しました(追加されたサンプルについては、これを解決して出力をInput_file自体に保存する私のアプローチです)

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

注:これは編集した出力をInput_file(s)自体に保存するためのテストにすぎません。プログラムのENDセクションと一緒にBEGINセクションを使用できます。メインセクションは特定の質問自体の要件に従ってください。

公平な警告:また、このアプローチはパスに新しい一時出力ファイルを作成するため、システムに十分なスペースがあることを確認してください、ただし最終結果では、これはメインのInput_fileのみを保持しますが、操作中はシステム/ディレクトリにスペースが必要です



以下は、上記のコードのテストです。

プログラムの実行例:以下が.txt Input_file(s)であると仮定しましょう:

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

次のコードを実行すると、

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

注:意図的にls -lhtrsystemセクションに配置して、出力ファイルを確認しています作成しています(一時ベース)。後で名前を実際の名前に変更するためです。

-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

awkスクリプトの実行が完了した後でls -lhtrを実行すると、そこには.txtファイルしか表示されませんでした。

-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


説明:上記のコマンドの詳細な説明をここに追加します:

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.
6
RavinderSingh13

私がこれをやろうとした場合、私はおそらくこのようなものに行くでしょう:

$ 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

最初に元のファイルをバックアップにコピーしてから、元のファイルへの変更の保存を操作することをお勧めしますが、そうすると、すべての入力ファイルのFILENAME変数の値が変更され、望ましくありません。

ディレクトリにwhatever.bakまたはwhatever.newという名前の元のファイルがあった場合、それらを一時ファイルで上書きするため、そのためのテストも追加する必要があることに注意してください。一時ファイル名を取得するためのmktempの呼び出しは、より堅牢になります。

この状況でFARがさらに役立つのは、他のコマンドを実行して「インプレース」編集部分を実行するツールです。これは、POSIXのsed、awk、grep、trなどに「インプレース」編集を提供するために使用できるためです。値を出力するたびにスクリプトの構文をprint > outなどに変更する必要はありません。シンプルで壊れやすい例:

$ 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

次のように使用します。

$ 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

このineditスクリプトの明らかな問題の1つは、複数の入力ファイルがある場合に、コマンドとは別に入出力ファイルを識別するのが難しいことです。上記のスクリプトは、すべての入力ファイルがコマンドの最後にリストとして表示され、コマンドが一度に1つずつ実行されることを前提としていますが、もちろん、次の場所で2つ以上のファイルを必要とするスクリプトには使用できません。時間、例えば:

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

または、argリスト内のファイル間で変数を設定するスクリプト。例:

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

読者のための演習として、より堅牢なままにしておきますが、xargsの概要は、堅牢なineditがどのように機能する必要があるかについての出発点として考えてください:-)。

3
Ed Morton

シェルソリューションはシンプルで、おそらく十分高速です。

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

これが遅すぎることが明確に示された場合にのみ、別のソリューションを検索してください。覚えておいてください:時期尚早の最適化はすべての悪の根源です。

0
user448810