GNU/Linux >> Linux の 問題 >  >> Linux

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

このスレッドの主な目的は、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 の方法の出発点としての概要 動作する必要があります:-)


Linux
  1. Linuxのwhoamiコマンドを例で説明

  2. 5つの実用的な例で説明されたAWK配列

  3. GNU Diff のパーセンテージ値

  1. awk を使用して変更を適所に保存する

  2. Vim でディレクトリ内のファイルの名前を変更できますか?

  3. gnuplot で .gnu ファイルをプロットする

  1. Awkとパターンを一致させるレコードを数えますか?

  2. スペースを含む AWK およびファイル名。

  3. AWK で行 (行) を減算する方法