GNU/Linux >> Tutoriels Linux >  >> Linux

Enregistrer les modifications en place avec NON GNU awk

Étant donné que l'objectif principal de ce fil est de savoir comment faire SAVE inplace dans NON GNU awk donc je poste d'abord son modèle qui aidera n'importe qui dans n'importe quel type d'exigence, ils doivent ajouter/ajouter BEGIN et END section dans leur code en gardant leur BLOC principal selon leurs besoins et il devrait alors faire la modification en place :

REMARQUE : La suite écrira toute sa sortie dans output_file, donc si vous voulez imprimer quoi que ce soit sur la sortie standard, veuillez uniquement ajouter print... instruction sans > (out) dans la suite.

Modèle générique :

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

Solution spécifique de l'échantillon fourni :

J'ai trouvé l'approche suivante dans awk lui-même (pour les exemples ajoutés, voici mon approche pour résoudre ce problème et enregistrer la sortie dans Input_file lui-même)

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

REMARQUE :il ne s'agit que d'un test pour enregistrer la sortie éditée dans Input_file(s) lui-même, on peut utiliser sa section BEGIN, ainsi que sa section END dans leur programme, la section principale doit être conforme aux exigences de se remettre en question.

Avertissement juste : De plus, puisque cette approche crée un nouveau fichier de sortie temporaire dans le chemin, il vaut mieux s'assurer que nous avons suffisamment d'espace sur les systèmes, bien qu'au résultat final, cela ne conservera que les fichiers d'entrée principaux, mais pendant les opérations, il a besoin d'espace sur le système/répertoire

Voici un test pour le code ci-dessus.

Exécution du programme avec un exemple : Supposons que les éléments suivants sont les .txt Fichier(s) d'entrée :

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

Maintenant, lorsque nous exécutons le code suivant :

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

REMARQUE : J'ai la place ls -lhtr en system section intentionnellement pour voir quels fichiers de sortie il crée (de manière temporaire) car plus tard, il les renommera avec leur nom réel.

-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

Quand on fait un ls -lhtr après awk le script est terminé avec l'exécution, nous ne pouvions voir que .txt fichiers là-dedans.

-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

Explication : Ajout d'une explication détaillée de la commande ci-dessus ici :

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.

J'irais probablement avec quelque chose comme ça si j'essayais de faire ceci :

$ 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

J'aurais préféré copier d'abord le fichier d'origine dans la sauvegarde, puis opérer sur cette sauvegarde des modifications apportées à l'original, mais cela modifierait la valeur de la variable FILENAME pour chaque fichier d'entrée indésirable.

Notez que si vous aviez un fichier original nommé whatever.bak ou whatever.new dans votre répertoire, vous les écraserez avec des fichiers temporaires, vous devrez donc également ajouter un test pour cela. Un appel au mktemp obtenir les noms de fichiers temporaires serait plus robuste.

La chose la plus utile à avoir dans cette situation serait un outil qui exécute toute autre commande et effectue la partie d'édition "inplace" car cela pourrait être utilisé pour fournir une édition "inplace" pour POSIX sed, awk, grep, tr, peu importe et ne vous obligerait pas à changer la syntaxe de votre script en print > out etc. chaque fois que vous voulez imprimer une valeur. Un exemple simple et 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

que vous utiliseriez comme suit :

$ 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 problème évident avec ce inedit script est la difficulté d'identifier les fichiers d'entrée/sortie séparément de la commande lorsque vous avez plusieurs fichiers d'entrée. Le script ci-dessus suppose que tous les fichiers d'entrée apparaissent sous forme de liste à la fin de la commande et que la commande est exécutée sur eux un à la fois, mais bien sûr, cela signifie que vous ne pouvez pas l'utiliser pour les scripts qui nécessitent 2 fichiers ou plus à une heure, par exemple :

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

ou des scripts qui définissent des variables entre les fichiers dans la liste d'arguments, par exemple :

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

Le rendre plus robuste reste un exercice pour le lecteur mais regardez le xargs synopsis comme point de départ pour savoir comment un inedit robuste aurait besoin de travailler :-).


Linux
  1. La commande whoami sous Linux expliquée avec des exemples

  2. Compter les enregistrements correspondant au modèle avec Awk ?

  3. Les tableaux AWK expliqués avec 5 exemples pratiques

  4. Valeur en pourcentage avec GNU Diff

  5. utiliser awk avec des conditions de valeur de colonne

Commande AWK sous Linux avec des exemples

Commande Linux awk avec 10 exemples

Renommer en masse le fichier Bash avec compteur ?

Capturer des groupes avec Awk ou Grep ?

Apprenez les scripts Bash multi-threading avec GNU Parallel

Comment soustraire des lignes (lignes) avec AWK