Tengo algo de experiencia con terminales Inix gracias a las pasantías científicas en las que he participado, principalmente usando algunas utilidades como grep
, awk
y, sed
pero hay una cosa que he estado tratando de descubrir durante un tiempo que realmente me haría mucho más eficiente. con los números que tengo que hacer.
Tengo un script run.awk
que realiza algunas manipulaciones en una gran colección de archivos de texto masivos. Tal como está, tomará el archivo chloride.out
, extraerá los datos y lo escribirá chloride.cm
.
¿Hay alguna forma de hacer que este script acepte *.out
y escriba *.cm
archivos basándose en la frase comodín inicial en Shell?
La cantidad de scripts que he escrito para procesar grandes cantidades de datos y que he tenido que hacer más de cien iteraciones es simplemente molesta.
Idealmente, me gustaría saber si hay alguna manera de poder hacer esto para todos mis scripts con algo a través del shell. Si no se puede automatizar en el Shell o en un equivalente, ¿puedo al menos automatizar mis awk
scripts de una manera similar a la que he descrito?
Respuesta1
Seguramente puedes hacer que awk maneje múltiples archivos mediante comodines. Una sugerencia sería dejarla run.awk
como una "función" genérica que toma un solo archivo y produce un solo archivo de salida, y luego llamarla desde otro script que luego podría encargarse de asimilar los archivos de entrada y salida.
Ejemplo
Este sería un script Bash, podemos llamarlo awk_runner.bash
.
#!/bin/bash
for ifname in *.out; do
ofname=${ifname/.out/.cm}
printf "IN: %s, OUT: %s\n" $ifname $ofname
printf "running run.awk with %s & %s\n\n" $ifname $ofname
run.awk $ifname $ofname
done
Ejecución de muestra
Creé un directorio de ejemplo con algunos archivos de prueba.
$ touch file{1..4}.out
Esto resultó en la creación de 4 archivos:
$ ls -1
file1.out
file2.out
file3.out
file4.out
Ahora ejecutamos nuestro script:
$ ./awk_runner.bash
IN: file1.out, OUT: file1.cm
running run.awk with file1.out & file1.cm
IN: file2.out, OUT: file2.cm
running run.awk with file2.out & file2.cm
IN: file3.out, OUT: file3.cm
running run.awk with file3.out & file3.cm
IN: file4.out, OUT: file4.cm
running run.awk with file4.out & file4.cm
Después de cada línea que comienza con "ejecutando..." nuestro script podría ejecutarse desde aquí.
Archivos en una lista
Digamos que en lugar de usar el comodín, *.out
teníamos un archivo con una lista de nombres de archivo, digamos:
$ cat filelist.txt
file1.out
file2.out
file3.out
file4.out
Podríamos usar esta versión modificada de nuestro script que usaría un while
bucle en lugar de un for
bucle. Ahora llamemos a esta variante del script awk_file_runner.bash
:
#!/bin/bash
while read ifname; do
ofname=${ifname/.out/.cm}
printf "IN: %s, OUT: %s\n" $ifname $ofname
printf "running run.awk with %s & %s\n\n" $ifname $ofname
run.awk $ifname $ofname
done < filelist.txt
Esta versión del script lee la entrada del archivo filelist.txt
:
done < filelist.txt
Luego, para cada vuelta del while
bucle, usamos el read
comando para leer una línea del archivo de entrada.
while read ifname; do
Luego realiza todo de la misma manera que el primer script, donde ejecutará el awk
script run.awk
mientras recorre cada línea del archivo.
Respuesta2
En lugar de escribir un contenedor de shell y generar una nueva instancia de awk para cada archivo que procese, puede hacerlo directamente en awk. Si ya tiene un script awk, puede acceder al archivo actual usando la variable FILENAME. Entonces, si ejecuta awk 'some commands' file1 file2
, podrá saber si está trabajando con el archivo1 o el archivo2 usando FILENAME. También puedes usar >
on print
/ printf
in awk. Entonces, si tienes un script awk como
/pattern/{ print $1,$3 }
podrías hacer fácilmente
/pattern/{ print $1,$3 > FILENAME".processed" }
o utilícelo FNR=1
para saber cuándo está en un archivo nuevo y cree una variable para realizar una manipulación más compleja en el nombre del archivo. Como reemplazar una .in
extensión con .out
, como en
sauer@humpy:/tmp$ grep . file*.in
file1.in:a
file1.in:b
file2.in:c
sauer@humpy:/tmp$ awk 'FNR=1{out=FILENAME;sub("\.in$",".out",out)} {print "processed"$0 > out}' file*.in
sauer@humpy:/tmp$ grep . file*.out
file1.out:processeda
file1.out:processedb
file2.out:processedc
Estoy usando grep .
aquí para mostrar el nombre del archivo y el contenido de varios archivos, lo cual también es un truco divertido. Pero lo importante es establecer el valor de la out
variable en una versión modificada de FILENAME
cuando FNR
cambia a 1 (por lo que estamos en la línea 1 del archivo) y luego redirigir todas las impresiones a out
. Tenga en cuenta que esto es ligeramente peligroso, ya que si no se coincide con la extensión, no se realizará ninguna sustitución, lo que provocará la sobrescritura de los archivos de entrada. Por lo tanto, sería bueno agregar una verificación a prueba de fallas para asegurarse de eso out != FILENAME
o algo así también. Esto se deja como ejercicio para el lector. ;)
Si necesita un archivo que contenga una lista de nombres de archivos, es más fácil ejecutarlo como
awkscript $(< /path/to/filename_list_file )
Que toma el contenido de filename_list_file
y lo coloca en la línea de comando.