Bienvenidos de nuevo a Byte Mind, en este caso vamos a hablar de AWK, uno de los comandos más poderosos de Linux debido a su capacidad de procesamiento de datos, a través del cual se pueden utilizar operadores lógicos, variables, funciones de impresión, etc.
AWK significa “Aho, Weinberger y Kernighan” y se utiliza principalmente para escanear y procesar patrones. Se puede utilizar para la búsqueda de patrones en uno o varios ficheros a la vez, en la salida de otros comandos. Vamos a ver como es su sintáxis básica y luego explicaremos las posibilidades de este comando así como ejemplos de las mismas.
Índice
Sintáxis básica de AWK
La sintáxis básica de AWK sería la siguiente:
1 |
$ awk options program file |
AWK puede tomar los siguientes argumentos
- -F fs -> se utiliza para especificar un separador de archivos.
- -f archivo -> se utiliza para especificar un archivo que contiene un script awk.
- -v var = valor -> se utiliza para declarar una variable.
Hemos generado un fichero con una serie de datos que utilizaremos en los ejemplos de este post e imprimiremos su contenido directamente con awk:
1 2 3 4 5 6 7 8 9 10 11 |
$ awk '{print}' awkexamples.txt john engineer sales 30000 julia director account 25000 bjorn manager purchase 20000 bhavesh engineer sales 30000 rajesh directory sales 40000 nairobi clerk account 20000 jack peon purchase 23000 deep clerk sales 20000 steven manager purchase 32000 sila engineer account 21000 |
En el primer ejemplo no utilizamos ningún patrón así que vamos a usar uno, por ejemplo, imprimir aquellos usuarios del departamento de cuentas:
1 2 3 4 |
$ awk '/account/ {print}' awkexamples.txt julia director account 25000 nairobi clerk account 20000 sila engineer account 21000 |
Visto un primer ejemplo, vamos a profundizar en cada una de las posibilidades que nos da este comando.
Como se ha indicado también es posible guardar nuestro código en un fichero y luego cargar el mismo con awk.
Utilizaremos el código del último ejemplo, supongamos que tenemos el siguiente fichero de awk:
1 2 |
$ cat command.txt /account/ {print} |
Y llamamos al mismo con awk para que aplique el mismo al procesar el fichero:
1 2 3 4 |
$ awk -f command.txt awkexamples.txt julia director account 25000 nairobi clerk account 20000 sila engineer account 21000 |
Operadores en AWK
Operadores matemáticos
En AWK es posible utilizar los siguientes operadores matemáticos
- +, -, *, / -> Suma, Resta, Multiplicación y División.
- % -> Operador módulo o resto.
- ++, — -> Operador de incremento y decremento.
- +=, -=, *=, /=, %= -> Al igual que en otros lenguajes como C. (x += 1 sería equivalente a x = x + 1)
Operadores de búsqueda
Como ya hemos visto en la sección anterior es posible realizar búsquedas en base a cadenas de texto, a continuación se muestran los posibles operadores a utilizar para ello:
- /string/ -> busca la cadena indicada.
- /^string/ -> busca la cadena al principio de la línea.
- /string$/ -> busca la cadena al final de la línea.
- $N ~ /string/ -> busca la cadena en el campo N.
- $N !~ /string/ -> busca la cadena a excepción del campo N.
- /(string1)|(string2)/ -> busca la cadena 1 o la cadena 2.
- /string1/,/string2/ -> busca todas las líneas entre cadena 1 y cadena 2.
Variables en AWK
AWK viene por defecto con algunas variables integradas para el procesado de archivos de texto. Vamos a verlas por partes en función de las carácterísticas de cada una de ellas.
Variables básicas
Un uso básico de variables permitiría indicar que campos queremos obtener de cada una de las líneas, para ellos se utilizarían las siguientes variables:
- $0 -> se utiliza para mostrar la línea completa
- $1-$n -> mostrará los campos (columnas) de la línea especificada
En el siguiente ejemplo, imprimiremos el primer campo de cada línea:
1 2 3 4 5 6 7 8 9 10 11 |
$ awk '{print $1}' awkexamples.txt john julia bjorn bhavesh rajesh nairobi jack deep steven sila |
Por defecto, AWK, utiliza el espacio o el TAB como separador, aunque habrá ocasiones en que esto no sea útil, para ello imprimiremos el fichero /etc/passwd, el cual utiliza el carácter : como separador.
En este caso podemos utilizar el parámetro -F para indicar el mismo, como se ve en el siguiente ejemplo:
1 2 3 4 5 6 7 |
$ awk -F: '{print $1}' /etc/passwd root bin daemon adm lp sync |
Variables integradas
Ya hemos visto que las variables de campo de datos ($1, $2…) son utilizadas para extraer datos de un campo en concreto, y que además deberemos lidiar con el separador FS.
Pero estas no son las únicas variables, existen más variables como son las siguientes:
- FIELDWIDTHS -> especifica el ancho del campo.
- FS -> contiene el carácter separador de campo (espacio o TAB por defecto) y se utiliza para dividir campos.
- OFS -> almacena el separador de campo de salida y se utiliza para separar los campos cuando AWK los imprime
- ORS -> almacena el separador de líneas de salida y se utiliza para separa las líneas cuando AWK las imprime
- RS -> almacena el carácter separador de la línea actual
Vamos a ver algún ejemplo de las mismas, por ejemplo, la variables OFS, por defecto, es el carácter espacio, pero puedes establecer esta variable para que especifique el separador que necesitas:
1 2 3 4 5 6 7 8 9 10 11 |
$ awk 'BEGIN{FS=" "; OFS="-"} {print $1,$3,$4}' awkexamples.txt john-sales-30000 julia-account-25000 bjorn-purchase-20000 bhavesh-sales-30000 rajesh-sales-40000 nairobi-account-20000 jack-purchase-23000 deep-sales-20000 steven-purchase-32000 sila-account-21000 |
En el ejemplo anterior, hemos sustituido el separador utilizado en el fichero, el cual indicamos con la variable FS, y hemos modificado el mismo por un – mediante la variable OFS. Además hemos utilizado la variable BEGIN, utilizada para el preprocesamiento de texto, pero tranquilos, esta la veremos en detalle más adelante.
Veamos otro ejemplo, para este, habrá ocasiones en que nos encontramos con campos distribuidos sin un separador fijo, en estos casos la variable FIELDWIDTHS puede resolver el problema.
Teniendo el siguiente contenido:
1 2 3 4 |
$ cat awkexamples2.txt 1234.56789 987-2.1125 98271.2290 |
Podemos separar los campos en bloques del mismo tamaño con FIELDWIDTHS
1 2 3 4 |
$ awk 'BEGIN{FIELDWIDTHS="3 4 3"}{print $1,$2,$3}' awkexamples2.txt 123 4.56 789 987 -2.1 125 982 71.2 290 |
Para el siguiente ejemplo, hemos generado otro fichero, donde los datos están distribuidos en varias líneas, y como separador entre los datos se usa una línea vacía
1 2 3 4 5 6 7 8 |
$ cat awkexamples3.txt Nombre apellido 1 calle 2 (12345) +34666666666 Nombre2 apellido2 2 calle 4 (41414) +34 655555555 |
Utilizaremos a continuación AWK para establecer la variables FS con el carácter de salto de línea (\n) y la variable RS como texto en blanco para que las líneas vacías sean consideradas separadores:
1 2 3 |
$ awk 'BEGIN{FS="\n"; RS=""} {print $1,$3}' awkexamples3.txt Nombre apellido (12345) +34666666666 Nombre2 apellido2 (41414) +34 655555555 |
Perfecto! ahora podemos ver los registros y campos de una forma más amigable.
Algunas variables más
Además de las variables que ya vimos anteriormente, existen otras que pueden ayudar a obtener más información:
- ARGC -> devuelve el número de parámetros pasados.
- ARGV -> devuelve los parámetros por la línea de comandos.
- ENVIRON -> es un arreglo de las variables de entorno del shell y sus respectivos valores.
- FILENAME -> devuelve el nombre del archivo que está siendo procesado.
- FNR -> a través de la misma se puede acceder al registro que está siendo procesado.
- IGNORECASE -> se utiliza para indicar que ignore las diferencias entre mayúsculas y minúsculas.
- NF -> especifica el número total de campos en la línea actual.
- NR -> especifica el número total de líneas en el fichero/stream a procesar.
Vamos a ver a continuación en ejemplo como funciona alguna de ellas.
En el primer ejemplo, vamos a obtener los parámetros pasados, así como el segundo parámetro pasado a awk
1 2 |
$ awk 'BEGIN{print ARGC,ARGV[1]}' awkexamples.txt 2 awkexamples.txt |
La variable ENVIRON devuelve las variables de entorno, y podemos especificar que variable queremos obtener
1 2 |
$ awk 'BEGIN{print ENVIRON["USER"]}' centos |
La variable NF especificará el último campo de cada línea, sin importar su posición:
1 2 3 4 5 6 7 8 9 10 11 |
$ awk 'BEGIN{FS=" "; OFS=":"} {print $1,$NF}' awkexamples.txt john:30000 julia:25000 bjorn:20000 bhavesh:30000 rajesh:40000 nairobi:20000 jack:23000 deep:20000 steven:32000 sila:21000 |
Con la variable FNR podemos ver la línea que está siendo procesada
1 2 3 4 5 6 7 8 9 10 11 |
$ awk 'BEGIN{FS=" "} {print $1, "FNR="FNR}' awkexamples.txt john FNR=1 julia FNR=2 bjorn FNR=3 bhavesh FNR=4 rajesh FNR=5 nairobi FNR=6 jack FNR=7 deep FNR=8 steven FNR=9 sila FNR=10 |
Si procesamos dos ficheros, veremos la diferencia entre NR y FNR
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ awk 'BEGIN{FS=" "} {print $1, "FNR="FNR, "NR="NR}' awkexamples.txt awkexamples2.txt john FNR=1 NR=1 julia FNR=2 NR=2 bjorn FNR=3 NR=3 bhavesh FNR=4 NR=4 rajesh FNR=5 NR=5 nairobi FNR=6 NR=6 jack FNR=7 NR=7 deep FNR=8 NR=8 steven FNR=9 NR=9 sila FNR=10 NR=10 1234.56789 FNR=1 NR=11 987-2.1125 FNR=2 NR=12 98271.2290 FNR=3 NR=13 |
La variable FNR nos mostrará la línea procesada en cada fichero de forma individual, mientras que la variable NR mostrará el total de líneas que han sido procesadas.
Variables definidas por el usuario
Las variables definidas pueden tener cualquier nombre, pero no pueden empezar con un número.
Se pueden definir de dos formas, mediante el parámetro -v o en el propio código, veámoslo con dos ejemplos:
En este primero lo definimos como parámetro:
1 2 |
$ echo | awk -v test="Welcome" '{print test}' Welcome |
En el segundo lo incluimos en el preprocesado de awk:
1 2 |
$ awk 'BEGIN{test="Welcome"; print test}' Welcome |
Pre Procesamiento de los datos
Para poder modificar el comportamiento de la salida, antes de que se lleve a cabo el procesamiento de los datos se utiliza la palabra clave BEGIN. Como ya se vió anteriormente, se utiliza también cuando se quiere definir una variable.
En este caso, como ejemplo, vamos a suponer que se quiere crear un título o cabecera para los resultados, utilizaremos BEGIN para definirlo antes de procesar el contenido del fichero:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ awk 'BEGIN {print "The file contents:"} {print $0}' awkexamples.txt The file contents: john engineer sales 30000 julia director account 25000 bjorn manager purchase 20000 bhavesh engineer sales 30000 rajesh directory sales 40000 nairobi clerk account 20000 jack peon purchase 23000 deep clerk sales 20000 steven manager purchase 32000 sila engineer account 21000 |
Como se puede apreciar, se utiliza BEGIN para definir el título “The file contents:”
Post Procesamiento de los datos
Para poder modificar el comportamiento de la salida, una vez ya han sido procesados los datos, se utiliza la palabra clave END.
Por ejemplo, vamos a añadir un footer a la salida de nuestro comando:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ awk 'BEGIN {print "The file contents:"} {print $0} END {print "File footer"}' awkexamples.txt The file contents: john engineer sales 30000 julia director account 25000 bjorn manager purchase 20000 bhavesh engineer sales 30000 rajesh directory sales 40000 nairobi clerk account 20000 jack peon purchase 23000 deep clerk sales 20000 steven manager purchase 32000 sila engineer account 21000 File footer |
Como se aprecia en el ejemplo, se añade el texto “File footer” al final de la salida, y también es posible utilizar ambas (BEGIN y END) a la vez.
Utilizando condicionales en AWK
El lenguaje de scripting AWK permite también el uso de condicionales if, else, else if.
Veamos el siguiente ejemplo:
1 2 3 4 5 6 7 |
$ awk '{ if ($4 > 30000) { print $1 " +30000" } }' awkexamples.txt rajesh +30000 steven +30000 |
En el anterior ejemplo imprimimos los usuarios que tienen un sueldo mayor de 30000.
Añadamos ahora un else para indicar también los que tienen un sueldo inferior a 30000:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ awk '{ > if ($4 > 30000) { > print $1 " +30000" > } else { > print $1 " -30000" > } > }' awkexamples.txt john -30000 julia -30000 bjorn -30000 bhavesh -30000 rajesh +30000 nairobi -30000 jack -30000 deep -30000 steven +30000 sila -30000 |
Vemos como nos ha devuelto una salida diferente en función del salario de cada usuario, añadamos como último paso una condición extra utilizando else if:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ awk '{ > if ($4 > 30000) { > print $1 " +30000" > } else if ($4 > 20000 && $4 < 30000) { > print $1 " +20000" > } else { > print $1 " -20000" > } > }' awkexamples.txt john -20000 julia +20000 bjorn -20000 bhavesh -20000 rajesh +30000 nairobi -20000 jack +20000 deep -20000 steven +30000 sila +20000 |
Vemos como es posible utilizar condicionales en función de lo que necesitamos obtener en cada momento, y como ya se vió, es posible guardar nuestra configuración en fichero para hacer más sencilla la carga de la misma.
Uso de bucles en AWK
Bucle while
Es posible también utilizar un bucle while para iterar sobre una condición.
Vámoslo en un ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ awk '{ > sum = 0 > i = 0 > while (i < 5) { > sum += $i > i++ > } > average = sum / 3 > print "Average: ",average > }' while.txt Average: 3.33333 Average: 5.33333 Average: 10 Average: 14 |
El ciclo se ejecutará y cada vez añadirá 1 a la variable sum hasta que esta sea igual a 4.
Es posible también utilizar la palabra clave break o continue para cortar el bucle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ awk '{ > sum = 0 > i = 0 > while (i < 5) { > sum += $i > if (i == 3) > break > i++ > } > average = sum / 3 > print "Average: ",average > }' while.txt Average: 3.33333 Average: 5.33333 Average: 10 Average: 14 |
Bucle for
Es posible también utilizar un bucle for para iterar sobre una condición.
Veámoslo en un ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ awk '{ > sum = 0 > for (i=1;i<5;i++) { > sum += $i > } > average = sum / 3 > print "Average: ",average > }' for.txt Average: 1.66667 Average: 2.66667 Average: 5 Average: 7 |
Al igual que en el bucle while, también es posible también utilizar la palabra clave break o continue para cortar el bucle.
Uso de funciones en AWK
Funciones integradas
Dentro de las funciones integradas en AWK podemos diferencias las mismas en función de si están destinadas a valores numéricos, cadenas de texto o de sistema.
Funciones básicas
En esta parte vamos a ver algunas de las funciones más básicas que se pueden utilizar en awk:
- print e1, e2, en… -> devuelve los valores de e1, e2, en.
- printf f, e1, e2, en… -> devuelve los valores de e1, e2, … con el formato especificado en f (por ejemplo “string %s”, $1).
- getline X -> lee el siguiente registro de entrada y se lo asigna a x, si no se especifica x se asignará a $0.
- close(f) -> cierra el archivo o pipe abierto con print, printf o getline.
- system(cmd) -> ejecuta el comando especificado y retorna el estado de la salida de este.
Funciones matemáticas
Si te gustan las matemáticas o quieres operar con valores numéricos tienes aqui algunas funciones integradas:
- sqrt(x) -> devuelve la raíz cuadrada del argumento.
- log(x) -> devuelve el logaritmo en neperiano del argumento (base e).
- exp(x) -> devuelve el valor de e elevado al argumento.
- int(x) -> devuelve la parte entera del argumento.
- cos(x) -> devuelve el coseno del argumento.
- sin(x) -> devuelve el seno del argumento.
- atan(x) -> devuelve el arco tangente del argumento.
- rand() -> devuelve un número aleatorio comprendido entre 0 y 1.
Veamos un ejemplo devolviendo la parte entera de un valor float:
1 2 |
$ awk 'BEGIN{x=int(2.3); print x}' 2 |
Funciones de string
Existen muchas funciones para el procesado de cadenas de texto, dejamos aqui algunas de ellas:
- length(x) -> devuelve la longitud del argumento.
- match(s,r) -> devuelve la posición de s en donde ocurre r, comenzando en 1. Si la cadena no existe devolverá 0.
- substr(s,m,n) -> devuelve la subcadena de s que comienza con la posición m y finaliza en la posición n.
- sub(r,t,s) -> sustituye la primera ocurrencia de t por r en la cadena s. Si no se especifica s se tomará todo el registro ($0).
- gsub(r,t,s) -> igual que sub, pero sustituyendo todas las ocurrencias existentes.
- split(s,array,sep) -> divide la cadena s en un array. Si no se especifica el valor de sep, se utilizará el valor de FS como separador.
- index(s1,s2) -> devuelve la posición de la cadena s1 en donde se encuentra la cadena s3. Si no existe devolverá 0.
- sprintf(f,e1,e2,en) -> devuelve la cadena resultante de imprimir los valores e1, e2, en… con el formato que se especifique en f.
- toupper(s) -> devuelve la cadena s convertida a mayúsculas.
- tolower(s) -> devuelve la cadena s convertida a minúsculas.
Veamos un ejemplo de la cadena “welcome” pasada por la función toupper:
1 2 |
$ awk 'BEGIN{x=toupper("welcome"); print x}' WELCOME |
Funciones creadas por el usuario
Además de utilizar las funciones integradas en awk, también es posible crear funciones personalizadas. Vamos a ver un ejemplo de ello a continuación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ awk ' > function test() { > printf "The user %s has a salary of %i$\n", $1,$4 > } > BEGIN{FS=" "} > { test() }' awkexamples.txt The user john has a salary of 30000$ The user julia has a salary of 25000$ The user bjorn has a salary of 20000$ The user bhavesh has a salary of 30000$ The user rajesh has a salary of 40000$ The user nairobi has a salary of 20000$ The user jack has a salary of 23000$ The user deep has a salary of 20000$ The user steven has a salary of 32000$ The user sila has a salary of 21000$ |
Hasta aquí por ahora, espero que les ayude en su día a día y les ayude a familiarizarse con este increíble comando de linux. Y como siempre, cualquier duda, aporte o sugerencia es bienvenida en la sección de comentarios.