GNU/Linux >> Linux Esercitazione >  >> Linux

Salva le modifiche in atto con NON GNU awk

Dal momento che lo scopo principale di questo thread è come eseguire SAVE inplace in NON GNU awk quindi sto pubblicando prima il suo modello che aiuterà chiunque in qualsiasi tipo di esigenza, hanno bisogno di aggiungere/aggiungere BEGIN e END section nel loro codice mantenendo il loro BLOCK principale secondo i loro requisiti e dovrebbe fare la modifica sul posto quindi:

NOTA: Di seguito scriverà tutto il suo output in output_file, quindi nel caso in cui desideri stampare qualcosa sullo standard output, aggiungi solo print... istruzione senza > (out) nel seguito.

Modello generico:

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

Soluzione del campione specifica fornita:

Ho escogitato il seguente approccio all'interno di awk stesso (per gli esempi aggiunti di seguito è il mio approccio per risolvere questo problema e salvare l'output in Input_file stesso)

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

NOTA:questo è solo un test per salvare l'output modificato in Input_file(s) stesso, si potrebbe usare la sua sezione BEGIN, insieme alla sua sezione END nel loro programma, la sezione principale dovrebbe essere conforme ai requisiti di specifiche domanda stessa.

Avvertimento equo: Inoltre, poiché questo approccio crea un nuovo file temporaneo in uscita nel percorso, è meglio assicurarsi di avere spazio sufficiente sui sistemi, anche se alla fine questo manterrà solo i principali Input_file, ma durante le operazioni ha bisogno di spazio su sistema/directory

Di seguito è riportato un test per il codice precedente.

Esecuzione del programma con un esempio: Supponiamo che i seguenti siano i .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

Ora, quando eseguiamo il seguente codice:

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

NOTA: Ho posto ls -lhtr in system sezione intenzionalmente per vedere quali file di output sta creando (su base temporanea) perché in seguito li rinominerà con il loro nome effettivo.

-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

Quando eseguiamo un ls -lhtr dopo awk script è terminato con l'esecuzione, potremmo vedere solo .txt file lì dentro.

-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

Spiegazione: Aggiunta di una spiegazione dettagliata del comando precedente qui:

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.

Probabilmente sceglierei qualcosa di simile se dovessi provare a fare questo:

$ 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

Avrei preferito copiare prima il file originale nel backup e poi operare su quel salvataggio delle modifiche all'originale, ma così facendo cambierei il valore della variabile FILENAME per ogni file di input che non è desiderabile.

Nota che se avevi un file originale chiamato whatever.bak o whatever.new nella tua directory, li sovrascriveresti con file temporanei, quindi dovresti aggiungere un test anche per quello. Una chiamata al mktemp ottenere i nomi dei file temporanei sarebbe più affidabile.

La cosa MOLTO più utile da avere in questa situazione sarebbe uno strumento che esegue qualsiasi altro comando e fa la parte di modifica "sul posto" poiché potrebbe essere utilizzata per fornire modifiche "sul posto" per POSIX sed, awk, grep, tr, qualunque cosa e non ti richiederebbe di cambiare la sintassi del tuo script in print > out ecc. ogni volta che vuoi stampare un valore. Un esempio semplice, fragile:

$ 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

che useresti come segue:

$ 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

Un ovvio problema con quel inedit script è la difficoltà di identificare i file di input/output separatamente dal comando quando si hanno più file di input. Lo script sopra presuppone che tutti i file di input appaiano come un elenco alla fine del comando e il comando viene eseguito su di essi uno alla volta, ma ovviamente ciò significa che non puoi usarlo per script che richiedono 2 o più file a un orario, ad esempio:

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

o script che impostano variabili tra i file nell'elenco arg, ad esempio:

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

Renderlo più robusto lasciato come esercizio per il lettore ma guarda al xargs sinossi come punto di partenza per come un robusto inedit dovrebbe funzionare :-).


Linux
  1. Partizionare un'unità su Linux con GNU Parted

  2. usando awk con le condizioni del valore della colonna

  3. Salva le modifiche sul posto con awk

  4. Posso rinominare i file in una directory con Vim?

  5. AWK e nomi di file con spazio al suo interno.

Comando AWK in Linux con esempi

Comando awk di Linux con 10 esempi

Impara gli script Bash multi-threading con GNU Parallel

Valore percentuale con GNU Diff

Traccia il file .gnu con gnuplot

Come sottrarre righe (linee) con AWK