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
このスレッドの主な目的は、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 -lhtr
をsystem
セクションに配置して、出力ファイルを確認しています作成しています(一時ベース)。後で名前を実際の名前に変更するためです。
-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.
私がこれをやろうとした場合、私はおそらくこのようなものに行くでしょう:
$ 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
がどのように機能する必要があるかについての出発点として考えてください:-)。
シェルソリューションはシンプルで、おそらく十分高速です。
for f in *.txt
do awk '...' $f > $f.tmp
mv $f.tmp $f
done
これが遅すぎることが明確に示された場合にのみ、別のソリューションを検索してください。覚えておいてください:時期尚早の最適化はすべての悪の根源です。