GNU/Linux >> Tutoriales Linux >  >> Linux

Guarde las modificaciones en su lugar con NON GNU awk

Dado que el objetivo principal de este hilo es cómo hacer SAVE in situ en NON GNU awk así que estoy publicando primero su plantilla que ayudará a cualquier persona en cualquier tipo de requisito, necesitan agregar/agregar BEGIN y END sección en su código manteniendo su BLOQUE principal según su requisito y debe hacer la edición en el lugar entonces:

NOTA: A continuación, se escribirá toda su salida en el archivo de salida, por lo que, en caso de que desee imprimir algo en la salida estándar, solo agregue print... sentencia sin > (out) a continuación.

Plantilla genérica:

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

Solución de muestra específica proporcionada:

Se me ocurrió el siguiente enfoque dentro de awk en sí mismo (para muestras adicionales, el siguiente es mi enfoque para resolver esto y guardar la salida en 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

NOTA:esto es solo una prueba para guardar la salida editada en Input_file(s), uno podría usar su sección BEGIN, junto con su sección END en su programa, la sección principal debe ser según el requisito de específico pregunta en sí.

Advertencia justa: Además, dado que este enfoque crea un nuevo archivo de salida temporal en la ruta, es mejor asegurarse de que tenemos suficiente espacio en los sistemas, aunque en el resultado final esto mantendrá solo los archivos de entrada principales, pero durante las operaciones necesita espacio en el sistema/directorio

A continuación se muestra una prueba para el código anterior.

Ejecución del programa con un ejemplo: Supongamos que los siguientes son los .txt Archivo(s) de entrada:

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

Ahora, cuando ejecutamos el siguiente código:

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: Tengo lugar ls -lhtr en system sección intencionalmente para ver qué archivos de salida está creando (de forma temporal) porque luego los renombrará a su nombre real.

-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

Cuando hacemos un ls -lhtr después de awk la secuencia de comandos se realiza con la ejecución, solo pudimos ver .txt archivos allí.

-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

Explicación: Agregando una explicación detallada del comando anterior aquí:

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.

Probablemente elegiría algo como esto si intentara hacer esto:

$ 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

Hubiera preferido copiar primero el archivo original en la copia de seguridad y luego operar sobre los cambios guardados en el original, pero hacerlo cambiaría el valor de la variable NOMBRE DE ARCHIVO para cada archivo de entrada que no es deseable.

Tenga en cuenta que si tuviera un archivo original llamado whatever.bak o whatever.new en su directorio, los sobrescribirá con archivos temporales, por lo que también deberá agregar una prueba para eso. Una llamada al mktemp obtener los nombres de los archivos temporales sería más sólido.

Lo MUCHO más útil en esta situación sería una herramienta que ejecute cualquier otro comando y haga la parte de edición "en el lugar", ya que podría usarse para proporcionar edición "en el lugar" para POSIX sed, awk, grep, tr, lo que sea y no requeriría que cambie la sintaxis de su secuencia de comandos a print > out etc. cada vez que desee imprimir un valor. Un ejemplo simple y frágil:

$ 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 usaría de la siguiente manera:

$ 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 problema obvio con ese inedit script es la dificultad de identificar los archivos de entrada/salida por separado del comando cuando tiene varios archivos de entrada. La secuencia de comandos anterior asume que todos los archivos de entrada aparecen como una lista al final del comando y el comando se ejecuta contra ellos uno a la vez, pero por supuesto eso significa que no puede usarlo para secuencias de comandos que requieren 2 o más archivos a la vez. un tiempo, por ejemplo:

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

o scripts que establecen variables entre archivos en la lista de argumentos, por ejemplo:

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

Hacerlo más sólido se deja como ejercicio para el lector, pero observe el xargs sinopsis como punto de partida de cómo un sólido inedit tendría que funcionar :-).


Linux
  1. Particionar una unidad en Linux con GNU Parted

  2. Primeros pasos con awk, una poderosa herramienta de análisis de texto

  3. Guarde las modificaciones en su lugar con awk

  4. Trazar archivo .gnu con gnuplot

  5. Cómo restar filas (líneas) con AWK

Comando AWK en Linux con ejemplos

Comando awk de Linux con 10 ejemplos

Aprenda secuencias de comandos Bash multihilo con GNU Parallel

usando awk con condiciones de valor de columna

¿Puedo cambiar el nombre de los archivos en un directorio con Vim?

AWK y nombres de archivo con espacio en él.