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


9

OPが編集を行ってInput_file(s)自体に操作を保存する必要があるという質問(SO自体)に遭遇しました。

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

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

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

この問題に対して私が試した/考えたこと:そのアプローチは.txtファイルのforループを通過し、singleを呼び出すのawkは面倒でお勧めできないプロセスです。不要なCPUサイクルを無駄にし、ファイルの数が増えると、それが増えるためです。スロー。

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

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



編集:ここにサンプルの例を追加するEd sirのコメントによると、このスレッドのコードの目的は、汎用のインプレース編集でも使用できます。

入力ファイルのサンプル:

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

1
興味深い適切なawk問題++
anubhava

1
@ RavinderSingh13これを適用するファイルがたくさんある場合はawk、(おそらくサブシェルで)または{...}囲まれたグループへの単一の呼び出しを使用してから、目的の出力ファイルに(各入力ファイルごとに)結果を書き込みます。またはすべての入力ファイルの結合ファイル)。次に、サブシェルまたはブレースで囲まれたグループの出力を、現在書き込まれているファイルにリダイレクトしますか?awkコマンドの後に入力ファイルの文字列を含めるだけで、すべてのファイル(または同様の何か)を順次処理しますか?
David

@ DavidC.Rankin、これについて返信いただきありがとうございます。ええ、私はあなたがサーと言っている類似の種類のものを投稿しました、私の答えはこの質問にも投稿されていますlemmeは同じサーに対するあなたの意見を知っています、乾杯。
RavinderSingh13

1
少し寝て考えた結果、2つのオプション(1)がawk {..} file1 .. fileX変更されたファイルの書き込みにあります。たとえばtemp01、次のファイルを処理する次の反復mv -f tmp01 input01では、変更されたデータで入力ファイルを上書きします。または(2)スクリプト./tmp/tmp01 ... ./tmp/tmp0Xの実行中に新しいディレクトリを書き込み、awkその./tmpディレクトリ内のファイルをループしてフォローアップしmv -f "$i" "input_${i##*[^0-9]}"ます。たとえば、(または古い入力ファイルを置き換えるために必要な拡張)
David C. Rankin

@ DavidC.Rankin、あなたの見解をここに教えてくれてありがとうございます。IMHOの最初のオプションは少しリスクがあるかもしれません。私たちはawkの完全なコード補完なしで何かをしているからです。あなたがその解決策についてあなたの考えを知らせることができれば感謝してください。
RavinderSingh13

回答:


6

このスレッドの主な目的は、非GNUでSAVEをインプレースする方法なawkので、最初にそのテンプレートを投稿します。これは、あらゆる種類の要件に役立つため、メインブロックを維持しながら、コードに追加/追加しBEGINENDセクションを追加する必要があります。要件とそれはインプレース編集を行う必要があります:

注:以下を実行すると、すべての出力がoutput_fileに書き込まれます。そのため、標準出力に何か出力したい場合は、以下のprint...ステートメントを追加しない> (out)でください。

一般的なテンプレート:

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)自体に保存するためのテストにすぎません。プログラムのBEGINセクションとENDセクションを使用できます。メインセクションは、特定の質問自体の要件に従ってください。

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



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

例を使用したプログラムの実行:以下が.txtInput_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

私たちが行うとls -lhtrした後、awk スクリプトを実行して行われ、我々は見ることができ.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.

1
面白い事実:入力ファイルをFNR==1ブロックで削除しても、変更をそのまま保存できます。のようにawk 'FNR==1{system("rm " FILENAME)} {print "new lines" > FILENAME}' files...。これはまったく信頼できません(完全なデータ損失が発生する可能性があります)が、それでもほとんど
正常に

1
非常によく説明された回避

3

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

$ 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がどのように機能する必要があるかについての出発点として考えます:-)。


0

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

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

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


お返事ありがとうございます。私の質問自体で述べたように、私たちはこの答えを認識していますが、これは実際にはこのタスクを実行するのはやり過ぎです。お時間をいただき、ありがとうございます。
RavinderSingh13
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.