GNU awk es una herramienta muy útil para modificar archivos, buscar y transformar datos y, en general, realizar cualquier tipo de tratamiento masivo de ficheros. Con un programa awk es posible contar el número de líneas de un archivo, seleccionar columnas, aplicar filtros, realizar cruces, borrar el último campo de cada línea, hacer sumarizaciones, comprobar duplicados, muestreos, etc.
Para aprender a utilizar AWK mediante esta pequeña guía, lo mejor es copiar los ficheros de ejemplo que aparecen y ejecutar las instrucciones o programas que se comentan para ver directamente cual es el resultado. Únicamente leyendo la guía es bastante más complicado entender el funcionamiento de AWK.
Índice de contenidos
Estructura y ejecución
Separadores de registros
Separadores de campos y columnas de tamaño fijo
Campos computados
Agrupación de acciones
Getline
Getline y ejecución de programas
Ficheros de salida
Expresiones
Conversiones de formato
Control del flujo
Arrays asociativos
Otras acciones/funciones
Funciones
Expresiones regulares
Rendimiento / Profiling
Nombres de columnas
Ordenar los registros de un fichero
Sumarizaciones
Cruces de ficheros (join match + unmatch)
Muestra aleatorias
Conexiones de red
Estructura y ejecución
Un programa awk presenta la siguiente estructura de reglas:
patrón { acción } patrón { acción } ...
Los patrones corresponden a expresiones regulares (p.ej. /foo/) o condiciones (p.ej. $1 = “Jan”) y las acciones son funciones del lenguaje.
En una regla puede omitirse el patrón o la acción, pero no ambos. Si el patrón se omite, entonces la acción se realiza para todas las líneas. Si se omite la acción, la acción por defecto es imprimir las líneas que cumplan el patrón.
Fichero_A.txt
Jan 13 25 15 115 Feb 15 32 24 226 Mar 15 24 34 228 Apr 31 52 63 420 May 16 34 29 208 Jun 31 42 75 492 Jul 24 34 67 436 Aug 15 34 47 316 Sep 13 55 37 277 Oct 29 54 68 525 Nov 20 87 82 577 Dec 17 35 61 401 Jan 21 36 64 620 Feb 26 58 80 652 Mar 24 75 70 495 Apr 21 70 74 514
Por ejemplo, para las lineas que contienen “Jan” las imprimimos por pantalla (simulamos comando ‘grep’):
awk '/Jan/ { print $0 }' Fichero_A.txt
Si quisiéramos únicamente imprimir la segunda columna utilizamos $1 (el resto las podemos encontrar en $2, $3, etc.):
awk '/Jan/ { print $1 }' Fichero_A.txt
También podemos realizar sumarizaciones, por ejemplo, sumamos los valores de la quinta columna de las lineas que tengan en la primera columna “Jan”:
awk '$1 == "Jan" { sum += $5 } END { print sum }' Fichero_A.txt
Como se puede observar, se han definido 2 reglas:
- Si la primera columna es “Jan” entonces acumulamos el quinto valor en una variable.
- Si es el final del fichero (END {…}), imprimimos el valor de la variable.
Es muy habitual ver ejemplos de AWK en una única línea, y justamente por ese motivo se ha ganado la fama de complejo. Pero en realidad podemos crear ficheros de texto con todas las instrucciones bien tabuladas y que permiten una mejor comprensión del objetivo del programa. Por ejemplo, podemos crear el fichero ‘test.awk’:
#!/usr/bin/awk -f $1 == "Jan" { sum += $5 } END { print sum }
* Los comentarios dentro del programa awk se marcan con “#”.
Para ejecutarlo, utilizaremos la siguiente sentencia:
awk -f test.awk Fichero_A.txt
O si el fichero tiene permisos de ejecución:
chmod 755 test.awk ./test.awk Fichero_A.txt
El primer argumento siempre corresponde al fichero sobre el cual vamos a ejecutar el programa. De hecho, desde el propio programa podemos acceder al listado de argumentos con el que se ha llamado:
#!/usr/bin/awk -f BEGIN { print ARGC # Número de argumentos print ARGV[0] # Siempre "awk" print ARGV[1] # Primer argumento print ARGV[2] # Segundo argumento }
Incluso, podríamos modificar los argumentos desde el propio programa para que no sea necesario especificar los ficheros por linea de comandos:
#!/usr/bin/awk -f BEGIN { ARGC = 2 # Uno más que el número de ficheros de entrada que indiquemos. ARGV[1] = "file1" } { print $0 }
Separadores de registros
Awk realiza un tratamiento registro a registro. Por defecto los registros se encuentran delimitados por el final de línea \n y por tanto, un registro = una línea. No obstante, podemos cambiar el símbolo que denota el final de un registro a otro carácter:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" RS = "1" } { print $0 }
En el anterior programa, hemos cambiado la variable RS de forma que awk compondrá el primer registro leyendo todos los caracteres hasta encontrar un “1” y así sucesivamente.
¿Qué utilidad tiene este mecanismo? Por ejemplo, en caso de que tengamos un fichero donde los registros se encuentren separados por lineas en blanco como el siguiente:
Fichero_Cortado.txt
Primer_registro dato1 dato2 dato3 dato4 Segudo_registro dato1 dato2 Tercer_registro dato1 dato2 dato3
Podemos establecer RS = “”, entonces awk compondrá los registros desde el primer carácter hasta que encuentra una línea en blanco. Veámoslo con un ejemplo:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_Cortado.txt" RS = "" } { print FNR " " NR " " $1 " " $2 " " $3 " " $4 " " $5 }
El anterior programa nos reformatearía el archivo a:
1 1 Primer_registro dato1 dato2 dato3 dato4 2 2 Segudo_registro dato1 dato2 3 3 Tercer_registro dato1 dato2 dato3
Si os fijáis, hemos utilizado la variable FNR que indica el número de registro del fichero actual y NR que hace referencia al número de registro total (solo tiene sentido en caso de que hayamos utilizado varios ficheros de entrada).
Separadores de campos y columnas de tamaño fijo
Por defecto awk identifica las columnas utilizando como separador el espacio o el tabulador (si hay varios consecutivos son ignorados). Es posible modificar este separador mediante la variable FS:
#!/usr/bin/awk -f BEGIN { FS = ";" }
Incluso podríamos utilizar expresiones regulares para indicar delimitaciones:
FS = ", \t" # Coma y tabulador FS = " " # Por defecto FS = "[ ]" # Un único espacio, si hay varios se consideraran campos vacíos
En el caso de que queramos modificar el delimitador de registros y columnas para la salida utilizaremos las variables ORS y OFS respectivamente:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" FS = " " # Delimitador columnas OFS = ";" RS = "\n" # Delimitador registros ORS = "[FIN]\n" } { print $1, $2, $3 }
Para la interpretación de ficheros que no disponen de delimitadores, sino que tienen columnas de tamaño fijo como por ejemplo:
Fichero_ColumnasFijas.txt
937563.51176.9000.0043.9600.0801 937663.52276.9060.0043.9620.0801 937763.51476.9360.0043.9660.0801 937863.53776.9240.0043.9670.0801 937961.96778.5430.0044.1090.0801 938061.95178.5420.0044.1080.0801
Podemos utilizar la variable FIELDWIDTHS, donde especificaremos en orden el tamaño de cada uno de los campos:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_ColumnasFijas.txt" FIELDWIDTHS = "4 6 6 5 5 5 1" # Tamaño de cada campo en orden } { print $1, $2, $3, $4, $5, $6, $7 }
Para hacer referencia al registro completo hemos utilizado $0, pero si queremos acceder a columnas concretas se utiliza $1, $2,… $NF (variable que hace referencia al último). Cabe mencionar que si la columna no existe, no se produce ningún error… simplemente se muestra una cadena vacía.
Si bien $NF apunta a la última columna, NF nos indica el número de columnas que tiene el registro. Podemos utilizar ese valor para acceder a un campo anterior. El siguiente ejemplo nos mostraría el penúltimo campo de cada registro:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } { print $(NF-1) }
De hecho, podemos utilizar $() para indicar dentro de los paréntesis cualquier tipo de operación (p.ej. $(2*2-1))
Campos computados
Los campos, además de ser mostrados, pueden ser modificados directamente o incluso creados (p.ej. campos computados):
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } { $2 = $2 - 10 } { $(NF+1) = "Nuevo campo" } { print $0 }
Agrupación de acciones
No es necesario tener una regla para cada acción ( {acción} {acción} … ), sino que es posible agrupar diversas acciones en una única regla:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } { $2 = $2 - 10 $(NF+1) = "Nuevo campo" print $0 }
O podemos poner todas las acciones en la misma línea pero separadas por “;”, aunque esta opción complica la lectura del código:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } { $2 = $2 - 10 ; $(NF+1) = "Nuevo campo" ; print $0 }
Getline
Como hemos indicado, para cada registro se ejecutan todas las reglas que definimos en nuestro programa. No obstante, podemos variar ese comportamiento utilizando getline:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } { print "Linea impar: " NR " " $0 getline print "Linea par: " NR " " $0 }
El anterior programa leerá el primer registro y lo imprimirá añadiendo “Linea impar”. A continuación, getline lee el siguiente registro y lo almacena en $0 (columnas en $1, $2… ) para imprimirlo de nuevo añadiendo “Linea par”. Finalmente, no es necesario volver a indicar getline dado que al llegar al final de programa automáticamente awk salta al siguiente registro.
Quizás nos interese que getline no “machaque” la información del registro actual almacenado en $0, para ello:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } { print "Linea impar: " NR " " $0 getline nueva print "Linea par: " NR " " nueva }
El nuevo registro se guardará en la variable “nueva” y por tanto podríamos hacer comparaciones de columnas con el anterior almacenado en $0. Por ejemplo, esto nos permitirá invertir el orden de los registros en grupos de dos:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } { getline nueva print "Linea nueva: " nueva print "Linea anterior: " $0 }
También podemos utilizar getline para leer un registro de un fichero que no haya sido indicado por linea de comandos, por ejemplo:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } { print $0 getline datos_aux < "/etc/passwd" }
Con esta característica podremos realizar cruces entre ficheros como veremos más adelante.
Es importante saber que el comando getline devuelve un 1 si encuentra un registro, y 0 si se encuentra el final del fichero. Por otra parte, si se produce algún error (p.ej. no existe el fichero) entonces getline devolverá un -1.
Getline y ejecución de programas
Getline también nos permite leer la salida de comandos mediante el uso de pipes (p.ej. "/bin/date" | getline fecha):
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } BEGIN { if (("/bin/date" | getline fecha) == 1) { print "Fichero generado: " fecha } else { print "Fichero generado en fecha desconocida" } close("/bin/date") } { print $0 }
En el ejemplo anterior, validamos que getline lee correctamente un registro e imprimimos la hora. La acción close permite cerrar pipes o ficheros y así, en caso de que volvamos a ejecutar el comando o a leer el fichero empezaría de nuevo desde cero.
Ficheros de salida
Hasta el momento hemos utilizado la acción “print” para mostrar registros por pantalla, si quisiésemos tener un mayor control del output podríamos utilizar “printf” de forma muy similar a como se realiza en el lenguaje C:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } NR == 1 { printf "Entero %i\n", $1 printf "Decimal %f\n", $1 printf "Caracter ASCII %c\n", $1 printf "Notación exponencial %e\n", $1 printf "Cadena %s\n", $1 printf "Octal %o\n", $1 printf "Hexadecimal %X\n", $1 # Modificadores printf "Cadena a la derecha en columna de 10: %10i\n", $1 printf "Cadena a la izquierda en columna de 10: %-10i\n", $1 printf "Número con 2 decimales %.2f\n", $1 }
Como hemos visto, tanto print como printf escriben por defecto a la salida estándar. Quizás nos interese hacer que la salida se escriba en un fichero o se redirija a un comando mediante una pipe:
#!/usr/bin/awk -f BEGIN { ARGC = 2 ; ARGV[1] = "Fichero_A.txt" } { print $0 > "001_fichero_salida.txt" # Fichero nuevo (borra si ya existia) print $0 >> "001_fichero_acumulativo.txt" # Append si el fichero existe print $0 | "/bin/cat" # Pipe a un comando print $0 | "sort -r > 001_fichero_salida.ordenado.txt" } END { close("/bin/cat") ; close("sort -r > 001_fichero_salida.ordenado.txt") }
Como se observa en el ejemplo anterior, podemos aprovechar las ventajas de las pipes para combinar la potencia de awk con otros comandos como “sort”. Hay que tener en cuenta que el comando no se ejecuta hasta que la pipe no es cerrada con close o por el fin del programa, esto implica que toda la salida se guarda en memoria mientras tanto.
Expresiones
Operadores aritméticos
Suma: x+y
Resta: x-y
Negación: -x
Multiplicación: x*y
División: x/y
Resto: x%y
Exponente: x**y
Comparaciones: x<y , x<=y, x>y, x>=y, x==y, x!=y, x~y (‘y’ debe ser una expresión regular, p.ej. /^test/)
Expresiones booleanas: &&, ||, ! (negación)
Conversiones de formato
Las conversiones entre números y cadenas son automáticas (en caso de que una cadena no pueda ser convertida se traduce como 0):
#!/usr/bin/awk -f BEGIN { edad = "27" edad = edad + 1.5 print "Tengo " edad " años." }
En el ejemplo anterior edad es inicializada con una cadena, a continuación se utiliza en una suma y se convierte automáticamente a numérico. Finalment print convierte el número a cadena para permitir la concatenación.
Control del flujo
Las estructuras de control de awk son muy parecidas a las del lenguaje C:
#!/usr/bin/awk -f BEGIN { # Condicional edad = "27" if (edad == 27) { print "Si" } else { print "No" } # Bucles i = 1 print "While" while (i < = 3) { print i i++ } print "Do while" do { i-- print i } while (i > 1) print "Bucle for" for(i=1; i< = 3; i++) { print i } }
En los bucles podemos utilizar "continue" para saltar a la siguiente iteración o "break" para finalizar. A nivel de programa, como awk ejecuta todas las reglas para cada registro de los ficheros de entrada, en cierta forma se trata de un bucle que podemos hacer saltar al siguiente registro con "next" o parar con "exit".
Arrays asociativos
Como estructura de almacenamiento de información ya hemos trabajado con variables, pero awk nos ofrece la posibilidad de usar arrays asociativos (diccionarios):
#!/usr/bin/awk -f BEGIN { v[0] = "Elemento" v[1] = "Cadena" v["Clave"] = "Valor" v["key"] = 25 # Comprobar la existencia de una clave if ("Clave" in v) { print "Clave encontrada!" } # Borrado de elementos delete v["Clave"] # Recorrer un array for(key in v) { print key " -> " v[key] } # Arrays multidimensionales w[0, 1] = ";-)" }
Otras acciones/funciones
Veamos otras acciones útiles de awk:
#!/usr/bin/awk -f BEGIN { ## Números srand() # Inicializa semilla con la fecha/hora actual print rand() # Decimal aleatorio entre 0 y 1 print int(2.45) # Parte entera num = sprintf("%.2f", 2.256821) # Redondeo a 2 decimales print num print sqrt(4) # Raiz cuadrada print "Exponencial " exp(1) " sinus " sin(2) " cosinus " cos(2) " logaritmo " log(2) ## Strings print index("cadena", "de") # Posición donde se encuentra "de" (0 si no lo encuentra) print length("cadena") # Tamaño print match("cadena", /$.*/) # Compara con expresión regular str = "1;2;3" split(str, v, ";") # Divide y guarda en un vector/array print v[1] " " v[2] " " v[3] sub(/;/, "-", str) # Substituye la primera aparición ";" por "-" print str gsub(/;/, "-", str) # Substituye todos los ";" por "-" print str print substr(str, 1, 3) # Desde la posición 1, devuelve los siguientes 3 caracteres print tolower("MAYUSCULAS") print toupper("minusculas") #Fechas y horas t1 = systime() # Fecha/hora actual t2 = mktime("2009 01 01 00 00 00") # YYYY MM DD HH MM SS print "Tiempo transcurrido desde 01-01-2009: " print "- Segundos: " t1 - t2 print "- Minutos: " (t1 - t2)/60 print "- Horas: " (t1 - t2)/60/60 print "- Dias: " (t1 - t2)/60/60/24 # Formatear hora/fecha actual print strftime("%Y-%m-%d %H:%M:%S", systime()) # 2009-02-14 17:46:28 }
Para el formateo de fechas se utiliza la siguiente nomenclatura:
%a %A Nombre del dia (p.ej. Lunes) %b %B Nombre del mes %d Día del mes %H Hora (24) %I Hora (12) %j Día del año (001-365) %m Mes %M Minuto %p AM/PM %S Segundo %u Número del día de la semana (lunes = 1) %U Número de la semana del año %Y Año
Funciones
Awk nos permite definir nuestras propias funciones:
#!/usr/bin/awk -f function p (str) { print str return 0 # Opcional } BEGIN { p("Hola mundo!") }
Esto nos permitiría hacer funciones genéricas que nos ahorren código repetitivo.
Expresiones regulares
^ Inicio de la cadena $ Final de la cadena . Cualquier carácter único (excepto newline) [] Conjunto de caracteres [MVX] Encaja con M, V o X [0-9] Encaja con un dígito [^] Conjunto de carácter complementario [^0-9] Encaja con cualquier carácter excepto un dígito | Alternativas A|[0-9] Encaja con "A" o un dígito () Se pueden utilizar paréntesis para agrupar expresiones (A|[0-9])Z Encaja con "A" o dígito, que uno u otro vaya seguido de Z * La expresión precedente se puede repetir 0 o más veces A* Encaja con nada o un número indefinido de A + La expresión precedente tendrá lugar 1 o más veces A+ Encaja con una o más A ? La expresión precedente no aparecerá o aparecerá 1 única vez
Para que awk no sea sensible a las mayúsculas/minúsculas podemos jugar con las acciones tolower/toupper o bien establecer la variable IGNORECASE a 1.
Rendimiento / Profiling
Podemos ejecutar nuestro programa con pgawk para que genere un fichero denominado “awkprof.out” donde para cada linea se indica el número de veces que ha sido ejecutada. Así podremos identificar aspectos a optimizar.
Nombres de columnas
Por defecto, awk nos permite acceder a las columnas mediante el uso de $1, $2, etc… pero esto puede ser un inconveniente si cambia el número o orden de las columnas de nuestros ficheros, dado que implicará un cambio forzado en las referencias que usamos en nuestro programa. Para solucionar ese problema, si disponemos de ficheros donde la primera linea es la cabecera y se anotan los nombres de las columnas, podemos crear un array que después nos permita acceder como sigue:
#!/usr/bin/awk -f BEGIN { ARGC = 1+1 # Uno más que el número de ficheros de entrada que indiquemos. ARGV[1] = "Fichero_Column.txt" } # Cabecera NR==1 { # Guardamos nombres de columnas for(i = 1; i< = NF; i++) { c[tolower($i)] = i } } # Podemos acceder a las columnas por $1, $2... o por $c["nombre_columna"] NR > 1 { print $c["mes"] }
De esta forma podremos acceder a las columnas usando $c[“nombre_columna”], independientemente que en el futuro la cambiemos de lugar en el fichero de entrada.
Ordenar los registros de un fichero
Utilizaremos una función para ordenar array de menor a mayor, tenemos dos opciones:
- según los elementos: asort(array)
- según los indices: asorti(array)
Ambas funciones “machacan” los índices originales por índices numéricos (1, 2, 3…), para evitar perder el array original podemos usar dos argumentos: asort(array_origen, array_destino)
Por ejemplo, para ordenar un fichero por dos campos determinados utilizando asorti:
#!/usr/bin/awk -f { # Queremos ordenar por los campos $2 y $1 y guardamos la linea original correspondiente original[$2,$1] = $0 } END { # En "ordenado" tendremos del 1 a n las claves $2,$1 ordenadas de menor a mayor n = asorti(original, ordenado) for (i = 1; i < = n; i++) { # Imprimimos las lineas originales pero siguiendo el nuevo orden print original[ordenado[i]] } }
Sumarizaciones
Para realizar una sumarización de un fichero por el campo $1 y sumando los valores del campo $3, podemos utilizar el siguiente código (no requiere que el fichero este ordenado):
#!/usr/bin/awk -f { # Si no es la primera vez que tratamos esta clave... if (count[$1] != "") { count[$1]++ col_sum[$1] += $3 } else { count[$1] = 1 col_sum[$1] = $3 } } END { for (k in count) { print k " " count[k] " " col_sum[k] } }
Podríamos añadir más arrays para hacer más sumarizaciones de columnas, o utilizar como índice más campos (p.ej. count[$1, $2]) si queremos utilizar varias columnas como campos clave.
Por supuesto, con este mismo ejemplo podríamos validar si existen duplicados dado que en “count” estamos guardando el número de apariciones.
Cruces de ficheros (join match + unmatch)
Veamos como podemos hacer el cruce de los siguientes ficheros utilizando el primer campo como clave primaria:
Fichero_A.sorted.txt
Apr 21 70 74 514 Apr 31 52 63 420 Aug 15 34 47 316 Feb 15 32 24 226 Feb 26 58 80 652 Jan 13 25 15 115 Jan 21 36 64 620 Jul 24 34 67 436 Jun 31 42 75 492 Mar 15 24 34 228 Mar 24 75 70 495 May 16 34 29 208 Nov 20 87 82 577 Oct 29 54 68 525 Sep 13 55 37 277
Fichero_B.sorted.txt
Apr Abril Aug Agosto Dec Diciembre Feb Febrero Jul Julio Jun Junio Mar Marzo May Mayo Nov Noviembre Oct Octubre
Es importante observar que ambos ficheros se encuentran ordenados por la clave primaria (que será la que utilizaremos para cruzar) y el Fichero_B no contiene duplicados (que será el que utilizaremos como fichero secundario en el cruce).
A continuación el código del cruce:
#!/usr/bin/awk -f BEGIN { ## Ficheros de entrada # - Ordenados por las claves que van a ser utilizadas # - El fichero secundario no puede tener duplicados primary_file = "Fichero_A.sort.txt" secondary_file = "Fichero_B.sort.txt" # Ficheros de salida match_file = "000_A_and_B.txt" primary_unmatch_file = "000_A_and_notB.txt" secondary_unmatch_file = "000_notA_and_B.txt" ARGC = 1+1 # Uno más que el número de ficheros de entrada que indiquemos. ARGV[1] = primary_file } { # Clave primaria del fichero primario primary_pkey = $1 # Si el campo clave del fichero primario es más grande que el del secundario, avanzamos el registro del secundario if (reg == "" || primary_pkey > secondary_pkey) { status = getline reg < secondary_file if (status == 1) { split(reg, r, " ") # Guardamos los campos en el array r[1..n] # Clave primaria del fichero secundario secondary_pkey = r[1] } } # Si no se ha llegado al final del fichero del secundario if (status == 1) { # Cruzan if (primary_pkey == secondary_pkey) { print $0, r[2] > match_file vmatch[primary_pkey] = 1 # Control para evitar detectar como no cruzado en el futuro } # Registro del primario no existe en el secundario if (primary_pkey < secondary_pkey && !vmatch[primary_pkey]) { print $0 > primary_unmatch_file } # Registro del secundario no existe en el primario if (primary_pkey > secondary_pkey && !vmatch[secondary_pkey]) { print reg > secondary_unmatch_file } } else { # Se ha acabado el fichero secundario, por tanto todo lo pendiente del primario no existe en el secundario print $0 > primary_unmatch_file } }
Como podemos observar, el código necesario para llevar a cabo el cruce es algo más extenso que los ejemplos que hemos visto hasta ahora. No obstante, dado que awk nos da una mayor granularidad que otras herramientas comerciales de tratamiento masivo como ACL o SAS, tenemos como ventaja una mayor eficiencia y un incremento en la flexibilidad y potencia.
El programa anterior nos genera tres ficheros de salida:
- “000_A_and_B.txt”: Registros que han cruzado
- “000_A_and_notB.txt”: Registros del fichero primario que no se encuentran en el secundario
- “000_notA_and_B.txt”: Registros del fichero secundario que no se encuentran en el primario
Tal y hemos visto, los fichero de entrada deben estar ordenados para que el cruce funcione correctamente. A continuación tenemos otro programa que realiza la misma funcionalidad y que no requiere que los ficheros estén ordenados. Para ello el fichero secundario será cargado completamente en memoria antes de iniciar el cruce, esto implica un mayor rendimiento en cuanto al tiempo de ejecución pero un consumo muy superior de memoria. Si tenemos suficiente memoria RAM o los ficheros con los que trabajamos no son excesivamente grandes, vale la pena considerar este algoritmo:
#!/usr/bin/awk -f BEGIN { ## Ficheros de entrada # - No es necesario que esten ordenados # - El fichero secundario no puede tener duplicados primary_file = "Fichero_A.sort.txt" secondary_file = "Fichero_B.sort.txt" # Ficheros de salida match_file = "000_A_and_B.txt" primary_unmatch_file = "000_A_and_notB.txt" secondary_unmatch_file = "000_notA_and_B.txt" ARGC = 1+1 # Uno más que el número de ficheros de entrada que indiquemos. ARGV[1] = primary_file # Cargamos el fichero secundario en memoria en el array sec while ((getline < secondary_file) > 0) { sec[$1] = $0 sec_matched[$1] = 0 # Array que será utilizado para identificar los reg. del secundario que no cruzan } } { # Registros que cruzan if (sec[$1]) { sec_matched[$1]++ # Marcamos registro secundario como cruzado # Añadimos el camo 2 del registro secundario a toda la linea del primario split(sec[$1], s, " ") print $0, s[2] > match_file } else { # Registros del primario que no existen en el secundario print $0 > primary_unmatch_file } } END { # Registros del secundario que no existen en el primario for(k in sec_matched) { if (sec_matched[k] == 0) { print sec[k] > secondary_unmatch_file } } }
Muestra aleatorias
Para la obtención de N registros aleatorios de un fichero, podemos utilizar la siguiente implementación del algoritmo R de Waterman, mediante el cual no es necesario conocer de antemano el número total de registros y por tanto solo tendremos que leer una vez el fichero:
#!/usr/bin/awk -f # Waterman's Algorithm R for random sampling # by way of Knuth's The Art of Computer Programming, volume 2 BEGIN { ARGC = 1+1 # Uno más que el número de ficheros de entrada que indiquemos. ARGV[1] = "Fichero_A.txt" n = 2 # Tamaño la muestra t = n srand() # Inicializamos semilla } # Si el numero de registros tratados es inferior al tamaño de la muestra NR < = n { # Construimos el pool inicial de muestras con los primeros registros consecutivos pool[NR] = $0 places[NR] = NR next } # Si el numero de registros tratados es superior al tamaño de la muestra NR > n { t++ # Decrementamos las probabilidades de que el siguiente numero aleatorio sea inferior al numero de muestras M = int(rand()*t) + 1 if (M < = n) { # Substituimos registro del pool rec = places[M] # Borramos la muestra previamente seleccionada delete pool[rec] pool[NR] = $0 # Añadimos la nueva muestra places[M] = NR } } END { if (NR < n) { print "El numero de registros es inferior al muestreo definido" > "/dev/stderr" exit } # Dado que asorti es una funcion que ordena alfabéticamente y no según el numero real: # Convertimos claves numericas del array pool a cadenas con ceros (p.ej. 1 -> 01) pad = length(NR) for (i in pool) { new_index = sprintf("%0" pad "i", i) newpool[new_index] = pool[i] } # Visualizamos la muestra en orden x = asorti(newpool, ordered) for (i = 1; i < = x; i++) print newpool[ordered[i]] }
Conexiones de red
Awk también nos permite realizar conexiones de red mediante el uso de la siguiente ruta:
"/inet/protocol/local-port/remote-host/remote-port"
El protocolo puede ser ‘tcp’, ‘udp’, or ‘raw’ y como puerto local podemos dejarlo a 0 para que el sistema lo establezca por nosotros.
Veamos un ejemplo de conexión a Yahoo Finance para obtener la cotización del índice IBEX35 y el cambio Euro-Dolar:
#!/usr/bin/awk -f BEGIN { FS = "," # Separador de campos NetService = "/inet/tcp/0/download.finance.yahoo.com/80" print "GET http://download.finance.yahoo.com/d/quotes.csv?f=snl1d1t1c4&e=.csv&s=^IBEX+EURUSD=X" |& NetService print "symbol,name,last,date,time,currency" while ((NetService |& getline) > 0) { gsub(/"/, "", $0) # Quitamos las " de los strings print $0 } close(NetService) }
hola!!
alguien podria ayudarme???
en una columna de datos numericos como podria restar el dato anterior???
PJ:
de lo siguiente sumar de la columna 2 y 3 siempre el dato anterior…
Sat-22:05:04-12/12/09 163652252 154440470
Sat-22:10:03-12/12/09 163656013 154443781
Sat-22:15:04-12/12/09 163666077 154453472
Sat-22:20:03-12/12/09 163685518 154472151
Sat-22:25:03-12/12/09 163691778 154477788
Sat-22:30:04-12/12/09 163699900 154484164
Sat-22:35:02-12/12/09 163704968 154488847
Sat-22:40:02-12/12/09 163708669 154492211
Sat-22:45:04-12/12/09 163713052 154496231
Sat-22:50:02-12/12/09 163717234 154500082
es decir de las 22:50, restar en la columna 2 lo de las 22:45, igual para la columna 3….
saludos…
Excelente tutorial, realmente se puede aprender con el, mugras gracias al autor
excelente documento… MIL GRACIAS…
Excelente documento… Mil GRACIAS
Genial, has resumido cosas que mucho libros en demasiadas páginas ni siquiera mencionan.
Excelente tutorial .. Muchisimas gracias,
Hola … soy novato en el tema y necesito que me puedan ayudar para el caso de “Cruces de ficheros (join match + unmatch)”. El ejemplo me sirve, pero con la diferencia que los archivos están separados por “|” … ingreso los códigos que aparecen pero nunca crea el archivo de match … será que habra´que agregar un RS = “|” en algún lugar …
Muchas gracias por la ayuda
Saludos ,
Patricio
si tengo un fichero con el siguiente texto:
juan comio galletas
juan comio pasta
pedro comio pan
jose comio pasta
como puedo con awk leer la linea previa a fin de identificar que el $1 juan esta en 2 o n lineas???
para que como resultado imprima:
juan comio galletas,pasta
pedro comio pan
jose comio pasta
ayuda porfa…..
Tutorial muy bueno, me ha servido para un par de cosillas que no sabía cómo se hacía en AWK. ¡Gracias!
Hola, estoy haciendo una shell que recibe parámetros y luego en su interior los guarda en variables:
PARAMETRO1=$1
PARAMETRO2=$2
Luego hago un cat en la misma shell y quiero mostrar la primera columna del resultado con awk
cat /var/log/xferlog* | grep ‘comercial’ | awk ‘{print ‘$1′}’
El problema es que el $1 lo interpreta como el parámetro que recibió y no como la primera columna del resultado
grep -h comercial /var/log/xferlog* | awk ‘{print $1}’
puedes usar awk “{print ‘$1’}” no repitas las ” que la shell los confunde a todos jajaja
Hola podrían ayudarme? Gracias
tengo una tabla similar
ID M1 M2 M3 M4 M5 M6 M7 M8 M9 Texto
3 629.0 314.0 164.0 392.0 224.0 360.0 466.0 741.0 295.0 Texto
7 84.0 439.0 46.0 117.0 139.0 55.0 135.0 20.0 31.0 Texto
8 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 Texto
9 1.0 0.0 6.0 0.0 0.0 7.0 0.0 1.0 0.0 Texto
14 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 Texto
Quisiera saber como eliminar las lineas en las que suma de cada valor de igual a 1. Por ejemplo las lineas 4 y 6
Muchas gracias por su atención saludos.
hola tengo una pregunta! ¿como podria cuando tengo un tiempo ejem: 2/5/2010; 10:50:15:10 y de eso quiero eliminar los segundos y milisegundos?