このスレッドの主な目的は、NON GNU awk
でインプレース SAVE を行う方法です。 だから私は最初にそのテンプレートを投稿しています。これはあらゆる種類の要件で誰にでも役立ちます。 BEGIN
を追加/追加する必要があります と END
要件に従ってメイン ブロックを保持するコード内のセクションで、インプレース編集を行う必要があります。
注: 以下はすべての出力を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(s) のみが保持されますが、操作中にはシステム/ディレクトリにスペースが必要です
以下は、上記のコードのテストです。
例によるプログラムの実行: 以下が .txt
であると仮定しましょう 入力ファイル:
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
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.
もし私がこれをやろうとするなら、私はおそらくこのようなものを使うでしょう:
$ 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
への呼び出し 一時ファイル名を取得する方が堅牢です。
この状況でもっと便利なのは、他のコマンドを実行し、「インプレース」編集部分を実行するツールです。これは、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
または引数リスト内のファイル間に変数を設定するスクリプト。例:
awk '{print $7}' FS=',' file1 FS=':' file2
より堅牢にするため、読者の演習として残しますが、xargs
を見てください。 堅牢な inedit
の方法の出発点としての概要 動作する必要があります:-)