GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Änderungen an Ort und Stelle mit NON GNU awk speichern

Da das Hauptziel dieses Threads darin besteht, wie man anstelle von SAVE in NON GNU awk vorgeht Also poste ich zuerst seine Vorlage, die jedem bei jeder Art von Anforderung helfen wird, sie müssen BEGIN hinzufügen/anhängen und END Abschnitt in ihrem Code, der ihren Hauptblock gemäß ihrer Anforderung beibehält, und es sollte dann die Inplace-Bearbeitung durchführen:

HINWEIS: Im Folgenden wird die gesamte Ausgabe in die Ausgabedatei geschrieben. Wenn Sie also etwas auf die Standardausgabe drucken möchten, fügen Sie bitte nur print... hinzu Anweisung ohne > (out) im Folgenden.

Generische Vorlage:

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

Spezielle bereitgestellte Probenlösung:

Ich habe mir in awk folgenden Ansatz ausgedacht selbst (für hinzugefügte Beispiele folgt mein Ansatz, um dies zu lösen und die Ausgabe in Input_file selbst zu speichern)

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

HINWEIS:Dies ist nur ein Test zum Speichern der bearbeiteten Ausgabe in Input_file(s) selbst, man könnte seinen BEGIN-Abschnitt zusammen mit seinem END-Abschnitt in seinem Programm verwenden, der Hauptabschnitt sollte den spezifischen Anforderungen entsprechen selbst hinterfragen.

Faire Warnung: Da dieser Ansatz eine neue temporäre Ausgangsdatei im Pfad erstellt, stellen Sie also besser sicher, dass wir genügend Speicherplatz auf den Systemen haben, obwohl dies letztendlich nur die Haupt-Input_file(s) behält, aber während des Betriebs Platz im System/Verzeichnis benötigt

Es folgt ein Test für den obigen Code.

Programmausführung anhand eines Beispiels: Nehmen wir an, es folgen die .txt Eingabedatei(en):

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

Wenn wir nun folgenden Code ausführen:

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

HINWEIS: Ich habe Platz ls -lhtr in system Abschnitt absichtlich, um zu sehen, welche Ausgabedateien erstellt werden (vorübergehend), da sie später in ihren tatsächlichen Namen umbenannt werden.

-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

Wenn wir eine ls -lhtr machen nach awk Skript mit dem Ausführen fertig ist, konnten wir nur noch .txt sehen Dateien darin.

-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

Erklärung: Fügen Sie hier eine detaillierte Erklärung des obigen Befehls hinzu:

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.

Ich würde wahrscheinlich so etwas nehmen, wenn ich versuchen würde, dies zu tun:

$ 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

Ich hätte es vorgezogen, zuerst die Originaldatei in die Sicherungskopie zu kopieren und dann die Änderungen am Original zu speichern, aber dies würde den Wert der FILENAME-Variablen für jede Eingabedatei ändern, was unerwünscht ist.

Beachten Sie, dass, wenn Sie eine Originaldatei mit dem Namen whatever.bak hatten oder whatever.new in Ihrem Verzeichnis, dann würden Sie sie mit temporären Dateien überschreiben, also müssten Sie auch dafür einen Test hinzufügen. Ein Anruf bei mktemp um die temporären Dateinamen zu erhalten, wäre robuster.

Das WEIT Nützlichere in dieser Situation wäre ein Tool, das jeden anderen Befehl ausführt und den "inplace" -Bearbeitungsteil ausführt, da dies verwendet werden könnte, um "inplace" -Bearbeitung für POSIX sed, awk, grep, tr, was auch immer und bereitzustellen würde es nicht erfordern, dass Sie die Syntax Ihres Skripts in print > out ändern usw. jedes Mal, wenn Sie einen Wert drucken möchten. Ein einfaches, zerbrechliches Beispiel:

$ 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

die Sie wie folgt verwenden würden:

$ 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

Ein offensichtliches Problem mit diesem inedit script ist die Schwierigkeit, die Eingabe-/Ausgabedateien getrennt vom Befehl zu identifizieren, wenn Sie mehrere Eingabedateien haben. Das obige Skript geht davon aus, dass alle Eingabedateien als Liste am Ende des Befehls erscheinen und der Befehl einzeln für sie ausgeführt wird, aber das bedeutet natürlich, dass Sie es nicht für Skripte verwenden können, die 2 oder mehr Dateien erfordern at eine Zeit, z. B.:

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

oder Skripte, die Variablen zwischen Dateien in der Argumentliste setzen, z. B.:

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

Um es robuster zu machen, bleibt als Übung für den Leser übrig, aber schauen Sie sich den xargs an synopsis als Ausgangspunkt dafür, wie ein robuster inedit müsste funktionieren :-).


Linux
  1. whoami-Befehl in Linux mit Beispielen erklärt

  2. Zählen Sie Datensätze, die mit Awk übereinstimmen?

  3. Bash-Datei Massenumbenennung mit Zähler?

  4. Gruppen mit Awk oder Grep erfassen?

  5. AWK-Arrays erklärt mit 5 praktischen Beispielen

Partitionieren Sie ein Laufwerk unter Linux mit GNU Parted

Daten extrahieren und anzeigen mit awk

AWK-Befehl in Linux mit Beispielen

Linux-awk-Befehl mit 10 Beispielen

Lernen Sie Multithreading-Bash-Skripte mit GNU Parallel

Plotten Sie die .gnu-Datei mit gnuplot