Jesús Alonso Abad Estrella Resa Camarero Autómatas y lenguajes formales Curso 2004-2005 Universidad de Burgos Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero 1. Introducción Awk es un lenguaje de búsqueda y procesamiento de patrones. Su nombre se debe a las iniciales de sus diseñadores: Alfred V. Aho y Brian W. Kernighan, ambos desarrolladores de los Laboratorios Bell. Inicialmente se desarrolló para escribir programas muy cortos, pero sus características se han impulsado más allá de la idea original de los autores. Awk está especialmente desarrollado para trabajar con archivos estructurados y patrones de texto. Dispone de características internas para descomponer líneas de entrada en campos y comparar estos campos con patrones que se especifiquen. Debido a estas posibilidades, resulta muy apropiado para trabajar con archivos que contengan información estructurada en campos, tales como inventarios, listas de correo y bases de datos simples. A pesar de que algunos programas en awk constan tan sólo de una línea, son el equivalente de comandos de un sistema Unix, tales como grep, wc, cut, join o paste. A diferencia de estas herramientas Unix, awk, al ser un lenguaje de programación, tiene disponibles funciones, estructuras de control y variables que aumentan su potencia y funcionalidad. 2. Sintaxis El cometido básico de awk es buscar líneas en fichero (u otras unidades de texto) que contengan ciertos patrones. Cuando en una línea se encuentra un patrón, awk realiza las acciones especificadas para dicho patrón sobre esa línea. Awk sigue el procesamiento de las líneas de entrada de esta forma hasta que llega al final del fichero. La sintaxis básica de awk es la siguiente: donde: archivo_programa: especifica el archivo fuente del programa a aplicar a archivo. c: especifica el carácter limitador de campos. Por defecto es el espacio en blanco. programa: conjunto de patrones e instrucciones a ejecutar. Para evitar conflictos con la shell, van encerrados entre comillas simples (‘). variable=valor: se utiliza para establecer los valores que tomarán las variables que utilice el programa. archivo: archivo que será procesado por awk. Si se especifica “-“, se interpreta como la entrada estándar. Awk también permite introducir comentarios dentro del código. Como ya conocemos, un comentario es un texto que es incluido dentro de un programa para que sea más entendible por los usuarios del mismo. En el lenguaje awk, los comentarios empiezan por el símbolo almohadilla “ #”. Ejemplo: # Este programa encuentra registros que contengan el patrón th. De esta forma es # como continúas el comentario en una línea adicional. 2 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero Ejemplo simple: programa que busca la cadena de caracteres foo en el fichero de entrada ‘ Lista-BBS’ y si la encuentra la imprime. awk ‘/foo/ {print $0}’ Lista-BBS 3. Campos y Variables La entrada de un programa awk se lee en unidades llamadas registros, y éstos son procesados por las reglas uno a uno. Cada registro leído se divide automáticamente en campos, para poder aplicar las reglas más fácilmente. El propósito de los campos es facilitar al usuario la referencia a las subpartes de un registro. La nomenclatura de los campos está compuesta por el símbolo dólar seguido del número de campo que se desee ($1, $2,etc). Símbolos de puntuación tales como el punto pueden ser incluidos dentro de un campo. $0 indica la línea entera. Estas variables se pueden manipular (sumar, cambiar, etc.) como cualquier otra variable, por ejemplo: awk '{ $1 = $2 + $3; print $0 }'archivo Este programa suma los campos dos y tres en el campo uno e imprime el nuevo registro (es decir, la línea completa representada por $0) El último campo de un registro puede ser representado por $NF. Si se intenta acceder a un número de campo mayor que el último, el resultado es la cadena vacía. Del mismo modo, el número de registros leídos se guarda en la variable NR. Los campos se pueden referir con expresiones numéricas. Si el resultado de evaluar la expresión no es un entero, se trunca la parte decimal. Un campo puede ser tratado como texto o como número, pero todo depende del contexto. Si se producen ambigüedades, awk tratará los campos como texto. Un ejemplo donde se requiere analizar el contexto es: x= “2”+”2” En este caso awk asigna a x el valor 4, como si hubiera escrito 2+2, ya que el contexto demanda un valor númerico. Los registros están separados por un carácter llamado separador de registros, que por defecto es el carácter newline. Por lo tanto, normalmente, un registro se corresponde con una línea de texto. Se puede usar un carácter diferente mediante la llamada a la variable RS (record separator), inicializada por defecto a “\n”. La variable RS puede tener cualquier cadena como valor, aunque sólo el primer carácter se tomará como el separador de espacios. El valor de RS se puede cambiar a través del operador de asignación ‘=’. El nuevo carácter separador estará entre comillas para que sea interpretado una constante cadena. Ejemplo: awk ‘BEGIN {RS = “ / ”}; {printf $0}’ Lista-BBS Por ejemplo, para la entrada Carlos:estudiante 3 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero Luis:profesor Con RS=”\n” generaría los registros $0= Carlos:estudiante $0= Luis:profesor Mientras que RS=”:” generaría $0= Carlos $0= estudiante Luis $0= profesor Nota: el momento adecuado para realizar el cambio del separador de campos por defecto a otro, es el comienzo del programa. Respecto a las variables, no hace falta indicar el tipo al que pertenecen, ya que esta operación se realiza automáticamente en función del contexto. Por defecto las variables se inicializan a la cadena nula o al valor numérico 0, por lo que no es necesario inicializarlas. Como ya hemos comentado, existen una serie de variables predefinidas en awk que pueden ser utilizadas dentro de un programa. Aparte de las ya mencionadas existen las siguientes: OFS( output file separator): separador del campo de salida. ORS ( output register separator): separador del registro de salida. RSTART: posición de la cadena en la que se verifica el patrón utilizado, comenzando desde 1. RLENGTH: longitud de la cadena en la que se verifica el patrón utilizado. SUBSEP: separador de cadenas en arrays multidimensionales. 4. Formato de los estamentos Se llaman estamentos a los conjuntos patrón-acción, que componen el núcleo de un programa awk, ya que especifican las acciones que se llevan a cabo cuando la entrada concuerda con cierto patrón. La sintaxis de los estamentos es: Patrón {acción} Para cada línea que verifique lo especificado en el patrón, se ejecutará la acción indicada. La acción siempre va encerrada entre llaves. Se pueden especificar varias acciones, separándolas por “;” o por el carácter de nueva línea “\n”. Si no se especifica la acción, se mostrarán por pantalla aquellas líneas que contengan el patrón. Dentro de las acciones puede haber sentencias de control, operaciones matemáticas o de concatenación. Los patrones deben ir rodeados por caracteres “/” y pueden contener más de un patrón separados por comas. En esta caso la acción se aplicará a aquellas líneas comprendidas entre la aparición del primer y último patrón definido. Si se utiliza el símbolo “ ~” se indica que el patrón de la derecha se incluye en el campo de la izquierda. Si el símbolo utilizado es el “!~”, el patrón de la derecha no es incluido en el campo de la izquierda. Ejemplos: 4 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero 1. $1 ~ /foo/ es la contraposición al ejemplo de patrón S1==”foo”, que indica que el primer campo del registro de entrada tiene que coincidir con foo. En nuestro caso, el patrón indicará que el primer registro de entrada tiene que incluir la palabra foo. Si ponemos el siguiente ejemplo, $1 ¡~/foo/, indicaremos que el primer campo del registro de entrada no debe contener la palabra foo. 2. Pongamos ahora un ejemplo más elaborado: awk ‘{ if ($1 ~ /J/) print }’ inventario Esta expresión regular selecciona todos los registros de entrada que contengan la letra ‘J’ en cualquier posición dentro del primer campo. 5. Patrones Los patrones controlan la ejecución de reglas: una regla es ejecutada cuando su patrón concuerda con el registro de la entrada actual. A continuación mostramos un sumario de los patrones soportados en awk: • Patrón vacío o null: casa con todos y cada uno de los registros de entrada. Ejemplo: awk ‘{print $1}’ Lista-BBS imprime el primer campo de cada registro. • / expresiones regulares /: La concordancia se produce cuando el texto del registro de entrada pertenece al lenguaje especificado por la expresión regular. Son usadas como patrón encerradas entre las barras “/”. Pueden combinarse con caracteres llamados operadores de expresiones o metacaracteres para incrementar su versatilidad. A continuación mostramos una tabla de ejemplo: Metacarácter ^ $ . […] [^…] | Significado Busca el principio de una cadena o de una línea dentro de una cadena Busca el carácter que concuerda al final o principio de una cadena Concuerda con cualquier carácter único, excepto el carácter nueva línea Ejemplo @capitulo p$ ---> concuerda con un registro que acabe en p U.A --> concuerda con cualquier expresión que comience con U y acabe en A Concuerda con cualquiera [0-9] ---> concuerda con de los caracteres cualquier dígito encerrados entre corchetes Concuerda con el [^0-9] ---> concuerda con conjunto de caracteres todo lo que no sea un complementario dígito Se usa para especificar ^p|[0-9] ---> concuerda alternativas con cualquier cadena que tenga un dígito o comience por p 5 Lenguaje awk () * + ? \ Jesús Alonso Abad Estrella Resa Camarero Agrupan expresiones regulares Repetición de la expresión regular precedente La expresión regular precedente debe concordar al menos una vez La expresión puede concordar una vez o ninguna Se usa pasa suprimir el significado especial de un carácter ph* wh+y --> concuerda con why, pero no con wy fe?d -->concuerda con fed o fd Los operadores de mayor preferencia son “*”, ”+” y “?”, seguidos por concatenación y |. Los paréntesis pueden hacer que el modo de agrupar los operadores cambie. • expresiones: se considera que se ha producido una concordancia con una expresión cuando su valor, convertido a número, es distinto de cero ( si es un número) o no nulo ( si es una cadena). Existen un par de casos especiales de expresiones como patrones: los patrones de comparación y los patrones booleanos. Patrones de comparación: chequean relaciones como igualdad entre dos cadenas o números. Se construyen a través de los operadores relacionales, cuyas formas coinciden con las propias del lenguaje C en los operadores de igualdad, desigualdad y comparación. Se incluyen dos nuevas formas propias del awk que son las siguientes: x~y Verdad si x concuerda con la expresión regular descrita por y x!~ y Verdad si x no concuerda con la expresión regular descrita por y Ejemplos: awk ‘$1 = = “foo” {print $2}’ Lista-BBS imprime el segundo campo de cada registro de entrada cuyo primer campo valga justamente ‘foo’. awk ‘$1 ~ /foo/ {print $2}’ Lista-BBS acepta cualquier registro con un primer campo que contenga la cadena ‘foo’. Patrones booleanos: es una expresión que combina otros patrones utilizando los operadores booleanos”o” (‘||’), ”y” (‘&&’) y “not” (‘!’). Los patrones que relacionan los patrones booleanos pueden ser expresiones regulares o cualquier otro tipo de expresión. Ejemplos: awk ‘/2400/ && /foo/’ Lista-BBS imprime todos los registros del fichero de entrada Lista-BBS que contengan tanto ‘2400’ como ‘foo’. awk ‘! /foo/’ Lista-BBS imprime todos los registros del fichero de entrada ListaBBS que no contengan la cadena ‘foo’. • pat1, pat2 : Un par de patrones separados por una coma especifica un rango de registros que encaja con rangos de registros de entrada consecutivos. Ejemplo: 6 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero awk ‘$1 == “on”, $1 == “off ” ’ imprime todos los registros entre ‘on’/’off’ ambos incluidos. • Patrones especiales BEGIN y END: Son etiquetas especiales que suministran a awk información para antes del inicio del procesamiento o al final del mismo. En el caso de BEGIN la acción indicada se ejecutará antes de leer cualquier entrada, mientras que la acción asociada a END se ejecutará una vez realizado el tratamiento de los registros de entrada. La etiqueta END suele utilizarse para imprimir resultados totales o cálculos realizados con los registros leídos. Ejemplo: awk ‘ BEGIN {print “análisis de foo”} /foo/ {++foobar} END { print “foo aparece”, foobar, “veces.”}’ Lista-BBS Este programa averigua cuántas veces aparece la cadena “foo” en el fichero de entrada Lista-BBS. BEGIN imprime un título para el informe. La segunda regla incrementa el valor de la variable foobar cada vez que se lee de la entrada un registro que contiene el patrón “foo”. Finalmente, END imprime el valor de la variable foobar al final de la ejecución. 6. Acciones: una acción consiste en una o más sentencias awk encerradas entre llaves. Cada sentencia especifica una cosa que se tiene que hacer. Las sentencias son separadas por el caracter de nueva línea o por el carácter punto y coma si conviven más de una sentencia en la misma línea. A continuación mostramos los tipos de sentencias soportadas en awk: • Expresiones: pueden llamar a funciones o asignar valores a variables. La ejecución de este tipo de sentencias simplemente calcula el valor de la expresión. • Sentencias de control: especifican el flujo de control del programa awk. Se pueden utilizar varias sentencias propias de C, tales como if-else, bucles for, while, dowhile y sentencias break y continue. A continuación pondremos un ejemplo con cada una de ellas: Sentencia if - else: determinar si un número es par o impar. if (x %2 ==0) print “ x es par” else print “ x es impar” Este es el formato de la sentencia if. Ahora ilustraremos cómo quedaría un programa awk si esta sentencia formara parte de la acción: awk ‘{ if ( x %2==0) printf “x es par”; else printf “x es impar”}’ Nota: si el “else” aparece en la misma línea que el cuerpo de if y no es una sentencia compuesta ( su cuerpo no va entre llaves) tenemos que separar la sentencia if de la else con el carácter punto y coma (;). 7 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero Sentencia while: imprimir los tres primeros campos de cada registro. awk ‘{ i = 1 while (i<=3) { printf $i i++ } }’ Sentencia do–while: imprimir cada registro de entrada diez veces. awk ‘{ i=1 do { printf $0 i++ } while (i<= 10) }’ Sentencia for: imprimir los tres primeros campos de cada registro de entrada. awk ‘{ for ( i=1; i<=3; i++) printf $i }’ Sentencia break: encontrar el divisor más pequeño de un número. awk ‘{ num=$1 for ( div =2; div*div <= num ; div ++) if ( num % div== 0) break if ( num % div == 0) printf “ el divisor más pequeño de %d es %d”, num, div else printf “%d es primo”, num }’ Sentencia continue : imprimir los números de 0 a 20 excepto el 5. awk ‘BEGIN { x=0 for ( x=0; x<=20; x++){ if ( x==5) continue printf (“%d”, x) x++ } print “ ” }’ Existen otras dos sentencias que son propias del lenguaje awk: Sentencia next: fuerza a awk a que detenga el procesamiento del registro actual y vaya a leer el siguiente. No se ejecuta ninguna regla más para el registro en curso ni las demás acciones de la regla actual. Ejemplo: 8 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero NF != 4{ printf (“line %d skipped : doesn´t have four fields “, FNR) > “/dev/stderr” next } Es un ejemplo de regla que se podría utilizar al principio del programa para que no procese entradas con más de cuatro campos. Nota: El uso de las sentencia next no está permitido dentro de una regla begin o end. Sentencia exit: hace que awk detenga la ejecución de la regla actual inmediatamente y el procesamiento de la entrada. Una sentencia exit puede tener un argumento de tipo númerico, que representa el código de estado de salida para el proceso awk. Por defecto, este código corresponde al estado de éxito, representado con el cero. Ejemplo: En este caso, al estar la sentencia next dentro de una regla begin, si se produce un error, se parará todo el proceso de entrada y no se leería nada. BEGIN { } if ((“date” | getline date_now)<0){ print “Can´t get system date” > “dev/stderr” exit 4 } • Sentencias compuestas: consisten en una o más sentencias encerradas entre llaves. Una sentencia compuesta tiene como cometido poner varias sentencias juntas en el cuerpo de una sentencia if,while, do o for. • Control de la entrada: usando la sentencia next ya explicada y la función getline. Función getline: permite leer la entrada bajo un control explícito establecido por el usuario. Tiene las siguientes formas: 1. Sin argumentos: para leer la entrada del fichero de la entrada actual. En esta caso se lee de forma normal, es decir, se procesa el siguiente registro de entrada y se divide en campos para su análisis. 2. Con argumento “variable”: se utiliza cuando el usuario quiere leer una línea sin que sea procesada por las reglas y patrones de awk. La línea elegida se guarda en la “variable” y no disparará la ejecución de ninguna regla. 3. Con argumento <“fichero”: se usa cuando el usuario no quiere leer el siguiente stream de entrada, sino la entrada de un fichero en particular, cuyo nombre es una cadena que contiene la variable “fichero”. • Sentencias de salida, print y printf: son acciones que permiten sacar por pantalla una parte o toda la entrada. La diferencia entre ellas está en que la sentencia print utiliza un formato estandarizado y simple, mientras que printf permite al usuario especificar el formato con el que quiere ver la salida por pantalla. La sentencia print tiene la siguiente forma: printf item1, item2… Por ejemplo, {print “linea uno\nlinea dos\n linea tres”} o {print $1 $2}. Simplemente se indica la lista de parámetros a imprimir. En cambio, la sentencia printf, incluye un parámetro llamado formato, que representa la especificación de cómo se deben imprimir los argumentos de la lista. El formato es esencialmente el que usa la función del mismo nombre en el lenguaje C. Ejemplo: {printf “%-10s %s\n”, $1, $2} 9 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero 7. Funciones definidas por el usuario La definición de funciones por el usuario puede aparecer en cualquier parte entre las reglas de un programa awk. Una función está compuesta por: Nombre_función: es el nombre de la función que se va definir. Puede estar formado por secuencias de reglas, dígitos y subrayados, siempre que no comience por un dígito. Lista_de_parámetros: lista de argumentos de las funciones y nombres de variables locales separadas por comas. Cuando se realiza una llamada a una función, los argumentos se utilizan para guardar los valores de los argumentos que se pasan a la función. Las variables locales se inicializan por defecto a la cadena vacía. Cuerpo_ de_la_función: consiste en sentencias awk. Es la parte más importante de la definición, ya que indica lo que tiene que hacer. Los argumentos son una forma de referirse a otras variables del programa, mientras que las variables locales se usan para guardar valores temporales del cuerpo de la función. Por lo tanto, la definición de una función tendrá la siguiente forma: function nombre_función ( lista_de_parámetros){ cuerpo_de_la_función } Nota: los nombres de argumentos no se distinguen sintácticamente de los nombres de las variables locales. Por ello, los primeros valores que forman la lista de parámetros corresponderán al número de argumentos que se pasan a la función, y el resto serán variables locales. Una llamada a una función consiste en el nombre de la función seguido por los argumentos entre paréntesis. Los argumentos son expresiones awk. Cada vez que se ejecute la llamada, los argumentos se evaluarán y los argumentos que pasaremos a la función serán los valores resultantes de dicha evaluación. Ejemplo: Regla que llama a la función: $3>0 Declaración de la función miprint: { miprint ($3) } function miprint (num) { printf “%6.3g”, num } Nota: no se permiten espacios en blanco entre el nombre de la función y el paréntesis de apertura de la lista de parámetros, ya que si hubiera algún tabulador o espacio, awk podría pensar que se desea concatenar la lista de argumentos con una variable. 10 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero 8. Arrays en awk Un array es una tabla de varios valores, llamados elementos. Los elementos se distinguen por sus índices, que pueden ser tanto cadenas como números. Cada array tiene un nombre, que no puede ser igual al nombre de una variable que se esté usando actualmente. El lenguaje awk posee arrays de una dimensión para almacenar grupos de cadenas o números relacionados entre sí. Su funcionamiento es similar al de los vectores de otros lenguajes de programación, pero con una diferencia significativa: en awk no hace falta indicar el tamaño del vector antes de empezar a usarlo. En los otros lenguajes, es necesaria una reserva previa de memoria para empezar a trabajar con un array. En cambio, en awk los vectores son asociativos o colecciones de pares compuestas por un índice y su valor del elemento del vector correspondiente. El orden de los mismos no tiene ningún significado. Ejemplo: Si tenemos el siguiente array de cuatro elementos: [ 8 foo “” 30 ], en awk podemos referenciar cada uno de sus elementos de la siguiente manera: Elemento 4 Elemento 2 Elemento 1 Elemento 3 Valor 30 Valor “foo” Valor 8 Valor “” Una de las ventajas de los arrays asociativos es que se puede añadir un nuevo elemento siempre que se quiera. Ene el ejemplo anterior, podemos perfectamente añadir un elemento décimo cuyo valor sea la cadena “número diez”. Otra ventaja es que los índices no tiene que ser enteros positivos, pudiendo una posición del vector ser referenciada por una cadena (por ejemplo, “one”) e incluso por una cadena y un número entero. Ejemplo: Elemento “one” Valor “un” Elemento 1 Valor “un” Para acceder a un elemento de un array, basta con poner el nombre del array seguido entre corchetes de la posición elegida: array [índice]. En el caso de que esa posición esté vacía, se devuelve la cadena nula. Si queremos recorrer todos los elementos de un vector, sólo tenemos que hacer un bucle for de la siguiente forma: for ( variable in array) cuerpo for (i=1;i<=NF;i++) used[$i]=1; Si por el contrario se quiere borrar un elemento de un array, se usará la sentencia delete: delete array [index]. Cuando un elemento es eliminado es como si nunca se hubiera referenciado y nunca se le hubiera dado un valor. Por lo tanto, si se trata de referenciar, el programa devolverá la cadena nula. 11 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero Awk también posee arrays multidimensionales, que funcionan de la misma forma, salvo porque tienen que ser referenciado por tantos índices como dimensiones se quiera que tenga el vector ( por ejemplo, un array bidimensional necesitará dos índices para ser referenciado). 9. Programas de ejemplo Ejecución de programas: Como ya se ha dicho antes en el apartado de sintaxis, para ejecutar awk utilizaremos la siguiente sintaxis: Con esto podremos ejecutar los programas de dos formas: A través de la línea de comandos (omitiendo “-f archivo_programa”), o bien a través de un script de awk (omitiendo “’programa’”). La primera forma, a través de la línea de comandos, nos fuerza a declarar todo el programa en la línea de comandos, encerrándole entre comillas simples. Bueno para programas cortos. La sintaxis quedaría así: La segunda, es más útil cuando los programas son extensos, y es escribiendo el programa en un archivo de texto (basta con utilizar el block de notas o el editor vi). La sintaxis sería así: Para definir la entrada del programa, podremos pasarle un archivo que queramos procesar. Si no le pasamos ningún archivo, procesará la entrada estándar. Existe otra forma de ejecutar awk en algunos sistemas UNIX, y es mediante scripts de la shell. Para ello, podemos usar el mecanismo de shell ‘#!’. Por ejemplo, el script “hola” con el siguiente contenido: #! /bin/awk –f BEGIN {print “Hola”} al ser ejecutado, se comportaría como si hubiésemos ejecutado: awk –f hola (recordad que el símbolo ‘#’ se usa para definir un comentario en awk, luego el archivo anterior sigue siendo un script de awk válido) 12 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero Veamos algunos programas de ejemplo. Mostrar la suma total de los tamaños de los archivos modificados por última vez en noviembre ls –l | awk ‘$6 == “Nov” { sum += $5 } END { print sum }’ El programa, genera el listado de archivos en la carpeta actual, pasándosela como parámetro a AWK. AWK entonces comparará el 6º campo (mes de la última modificación) con la cadena de caracteres “Nov”. Si se cumple la igualdad, ejecutará la siguiente acción (incrementar sum en el valor del 5º campo –tamaño del archivo). Por último, cuando no queden más registros que analizar (END), ejecutará la acción: Mostrar por pantalla el valor de la variable sum –suma del tamaño de todos los archivos que fueron modificados por última vez en Noviembre. Programa que repita todos los registros que contengan la subcadena “th” awk ‘/th/’ Este programa analiza los registros buscando el patrón /th/, y ejecuta la acción por defecto { print $0 } que muestra el registro completo. El patrón viene definido por una expresión regular, que se encuentra entre las dos barras /.../. Cuando un registro cumpla este patrón, se ejecutará la acción. Así, para la entrada: Cathy Tom William Thomas The car is stopped AWK mostrará: Cathy Thomas The car is stopped Programa que muestre los campos 1 y 2 de una lista y les muestre separados por “;” y con una línea en blanco entre ellos awk ‘BEGIN { OFS=”;”; ORS=”\n\n” } { print $1, $2 }’ Lista-BBS Cuando ejecutemos AWK, se llamará a la acción asociada a BEGIN. Ésta, cambiará los valores por defecto de OFS y ORS (“ “ y “\n” respectivamente), para cambiar el Output Field Separator –Separador de Campos de Salida- a un “;”, y el 13 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero Output Registry Separator –Separador de Registros de Salida- a un “\n\n” (nueva línea y línea en blanco). Después, para todo registro leído (no tiene asociado ningún patrón), se mostrarán los campos 1º y 2º. La coma en el print indica que entre ambos campos deberá colocar un OFS, y al terminar print colocará un ORS. Luego nuestra salida tendrá los campos separados por un “;” y con una línea en blanco entre ellos. Programa que muestre una lista en una tabla formateada de dos columnas awk ‘BEGIN {printf “%-10s %s\n”, “Name”, “Number”; printf “%-10s %s\n”, “----“, “------“ } {printf “%-10s %s\n”, $1, $2}’ Lista-BBS Al ejecutar AWK, la acción BEGIN imprimirá el encabezado de la tabla. Después, para todo registro leído, mostrará los campos 1º y 2º con justificado, de modo que los campos quedarán alineados. Programa que muestra los registros en un fichero en orden inverso awk ‘{ a[NR] = $0 } END { for (i = NR; i>0; --i) print a[i] }’ El programa utiliza el valor de la variable NR para identificar el número de registro (en este caso el número de línea), y almacenar dicho registro en esa posición dentro de un array (almacena los registros en memoria) Una vez ha terminado de leer todo el fichero de entrada (END), le muestra por pantalla recorriendo el array en sentido contrario. Programa que muestre ordenada una lista desordenada awk ‘{if ($1>max) max=$1 arr[$1]=$0 } END { for (x=1; x<=max; x++) print arr[x] }’ El programa recibe como entrada una lista con dos campos: La posición dentro de la lista, y el contenido de la lista en esa posición. 14 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero Recorre todos los campos. Al no tener un patrón, la acción se ejecuta para cada registro leído. Lo que hace entonces es: En primer lugar, si es la posición más “alejada” que ha encontrado hasta ahora, actualiza la posición máxima. Entonces, pone el valor del array en la posición que indique el campo 1 (posición en la lista) con el valor de todo el registro. Una vez haya rellenado todo el array (no quedan registros por leer), se ejecuta la acción asociada a END, que es un bucle que muestra el contenido de esa lista/array. Para la entrada: 4 A mí me ha tocado el cuarto 2 Y yo el segundo 5 Y yo el último 3 Pues yo el tercero 1 Soy el primero generará la salida: 1 Soy el primero 2 Y yo el segundo 3 Pues yo el tercero 4 A mí me ha tocado el cuarto 5 Y yo el último Programa que recibe números y muestre los mayores de 0 alineados a 6 caracteres a la derecha y con tres dígitos para la parte decimal awk ‘function miprint(num) { printf “%6.3g\n”,num } $1>0 {miprint($1)}’ Primero definimos la función miprint(num), que recibe un número, y le imprimirá formateado usando printf. Después, para todo registro mayor que 0, llamará miprint, pasándole dicho número como parámetro, mostrando así el número con ese formato. Cálculo de factoriales de números naturales con awk awk ’function fact(num) { if (num <= 1) return 1 else return num * fact(num - 1) } { print $0 " factorial is " fact($0) }’ El programa define la función fact() que calcula el factorial de un determinado número recursivamente. Para cada registro de entrada, calculará su factorial. 15 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero 10. AWK AVANZADO 1. Referenciando campos sin usar constantes: Como ya dijimos en el manual básico, cuando un programa awk lee un registro de entrada, éste se divide en campos. Dichos campos son referenciados mediante el símbolo $ y un número entero, que corresponde a la posición que ocupa respecto al registro. Sin embargo, estos campos pueden ser también referenciados por cualquier expresión del lenguaje. El valor de dicha expresión especificará el número del campo. En el caso de que sea una cadena, ésta se convertirá a número. Ejemplo: awk ‘ { print $(2*2)} ‘ Lista_BBS En este caso, la expresión 2*2 se evaluará para obtener el valor del campo deseado ( que será el campo número 4). El uso de paréntesis tiene una gran importancia, ya que indican que la evaluación de la expresión numérica se tiene que realizar antes que la operación $. Si el número de campo toma el valor 0, se obtendrá el registro entero. Los números de campo negativos no se permiten. 2. Cambiando los contenidos de un campo: Asimismo, los contenidos de un campo se pueden cambiar. Pero cuidado, awk no cambiará el fichero de entrada original. Ejemplo: awk ‘{ $3=$2-10; printf $2,$3}’ inventario El signo – representa la sustracción, de modo que este programa reasigna al valor tres el resultado de evaluar la expresión $2-10, imprimiendo los nuevos valores de los campos al final. Para poder realizar esta operación, el contenido de un campo deber ser numérico, o en el caso de ser una cadena, susceptible de ser convertida a número. El valor resultante de la evaluación de las expresiones se convierte otra vez a cadena cuando se asigna a un campo. Una consecuencia del cambio de los contenidos de un campo es que el texto del registro de entrada es recalculado para obtener el nuevo campo en la posición en la que estaba el antiguo. Por lo tanto, $0 cambiará para reflejar la alteración. Ejemplo: awk ‘{$2=$2-10; printf $0}’ inventario En este programa se imprime una copia del fichero de entrada restándole 10 unidades a cada uno de los valores que representa el campo número dos para todas las líneas. 16 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero 3. Campos fuera de rango: Otra operación interesante con los campos es dar valores a campos que estén fuera del rango. Ejemplo: awk ‘ {$6 =($5+$4+$3+$2); print $6}’ inventario Acabamos de crear un nuevo campo $6, cuyo valor asociado es el resultado de la suma de otros campos . La creación de un nuevo campo cambia la copia interna del registro de entrada actual (el valor $0).Por lo tanto, si ahora imprimimos el valor de dicho campo, se añadirá el nuevo campo creado. Esta recomputación afecta sobre todo al separador de campos de salida, OFS, que tendrá que añadir nuevos separadores entre el registro nuevo y los previos. Otro valor que se ve afectado es el NF o número de campos que se fijará al mayor valor de los campos nuevos que se hayan creado. Sin embargo, el valor que devuelve awk cuando se referencia a un campo fuera del rango no cambia ( sigue devolviendo la cadena nula). Ejemplo: if( $(NF+1) !=” ”) print “no puede ocurrir” else print “ todo es normal” Se imprimirá la cadena “todo es normal” ya que NF+1 está fuera de rango. 4. Utilizando pipes: Un pipe es una forma de enlazar la salida de un programa con la entrada de otro. En awk se suele utilizar sobre todo para modificar la forma de leer la entrada. Ejemplos: awk ‘{ if ($1= = “@execute”){ tmp = substr ($0,10) while((tmp | getline)>0) print close (tmp) }else print }’ Este programa copia la entrada a la salida, excepto las líneas que comienzan por @execute, las cuales son reemplazadas por la salida producida por la ejecución del resto de la línea como un comando de shell. Obsérvese que tmp se ejecuta como un comando de shell y su salida es pipeada dentro de awk para que sea usada como entrada. La función close se usa para asegurarnos que de que si aparecen dos líneas @execute idénticas, el comando se ejecute de nuevo para cada línea. awk ‘BEGIN { 17 Lenguaje awk }’ Jesús Alonso Abad Estrella Resa Camarero “date” | getline tiempo_actual close (“date”) printf “Report printed on” tiempo_actual El programa lee la hora y el día actual a la variable tiempo_actual, usando la utilidad date, y después la imprime. 5. Redireccionando la salida de un programa awk: El resultado de la ejecución de las sentencias print o printf se puede mandar a un sitio que no sea la pantalla. Hay tres formas diferentes de redireccionar la salida: * print items > fichero salida: este tipo de redirección imprime los items en un fichero de salida en el que previamente se ha destruido todo su contenido previo. Si dicho fichero no existe, se crea. Ejemplo: awk ‘{printf $2 > “lista-teléfonos” printf $1 > “ lista-nombres”}’ Lista-BBS Este programa escribe una lista de nombres en un fichero de nombres y una lista de teléfonos en un fichero homónimo. Cada fichero de salida contendrá un nombre o un número de teléfono por línea. *print items >> fichero salida: la diferencia con la forma anterior es que el contenido previo del fichero no se destruye, sino que los nuevos items leídos se añaden al final del mismo. *print items | comando: este tipo de redireccionamiento abre un pipe a comando y escribe los valores de items a través de ese pipe. Ejemplo: awk ‘{ print $1 > “names. unsorted” print $1 | “sort –r > names.sorted” }’ Lista-BBS Este programa produce dos ficheros: una lista sin ordenar de nombres y una lista ordenada en orden alfabético inverso. La lista desordenada se escribe con una redirección normal, mientras que la ordenada se escribe con un pipe al comando sort de Unix ( dicho comando es el que realiza la acción de ordenar). 6. Cerrado de ficheros de salida y pipes: En un programa de ejemplo, anterior, ya hemos ilustrado cómo se ejecuta la función close, donde se utilizaba para volver a ejecutar un comando. Dicha función también se puede utilizar para cerrar ficheros de salida y pipes. La sintaxis es: close ( nombre-fichero) o close (comando). Por ejemplo, si hemos abierto este pipe print $1 | “sort –r > names.sorted” deberemos cerrarlo con lo siguiente close (“sort –r > names.sorted”). Es decir, el valor del argumento de la función close debe coincidir con la cadena usada para abrir el pipe o el fichero. 18 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero Las ocasiones en que vamos a tener que cerrar un fichero de salida son bastante numerosas, ya que para poder leerle tenemos antes que cerrarle, y no podemos dejar abiertos un número indeterminado de ficheros dentro del mismo programa awk, ya que podríamos exceder el límite del sistema de ficheros abiertos por proceso. Otro caso en el que debemos cerrarle es cuando volvemos a ejecutar el mismo programa varias veces con los mismos argumentos. Un pipe se debe cerrar para que un comando acabe. Mientras el pipe esté abierto, el comando intentará leer la entrada del pipe. Así pues, para que el comando lleve a cabo su trabajo, es conveniente cerrarle. Por ejemplo, si redireccionamos la salida a un programa mail, el mensaje realmente no se envía hasta que el pipe haya sido cerrado. 7. Funciones implícitas Awk cuenta con numerosas funciones implícitas (incluidas en el propio lenguaje). Se llaman de la misma manera que las funciones definidas por el usuario. Aquí se ofrece una lista con las funciones implícitas de awk y su descripción: Numéricas: int(x) Sqrt(x) Exp(x) log(n) sin(x) Cos(x) Atan2(y, x) rand() srand(x) time() Retorna la parte entera de x redondeada hacia abajo Raíz cuadrada positiva de x (x no puede ser negativo) Exponencial de x Logaritmo natural de x Seno de x (x en radianes) Coseno de x Cotangente de y/x Número aleatorio entre 0 y 1 Coloca la semilla de aleatoriedad (el valor que usará como referencia para generar los números aleatorios) con el valor de x. Si se omite x, utiliza la fecha y hora del sistema (para conseguir la máxima aleatoriedad). Devuelve la hora actual en segundos desde el 1-1-1970 Cadenas: index(cadena, subcadena) Devuelve la posición de subcadena dentro de cadena. La posición inicial es 1. Devuelve 0 si subcadena no está contenida en cadena. length(cadena) Devuelve la longitud de la cadena. Si se omite cadena devuelve la longitud de $0 match(cadena, expreg) Función similar a index(), salvo que en esta ocasión, la subcadena viene definida por una expresión regular expreg. Devuelve la posición en la que se encuentra la subcadena más larga y más a la izquierda que concuerda con la expresión regular, o 0 si no encuentra ninguna. Nota: awk cuenta con dos variables implícitas, RSTART y RLENGTH, donde match almacenará los valores de la posición y longitud de la subcadena respectivamente (el valor que devuelve match coincidirá con el de RSTART) split(cadena, vector, Divide cadena en subcadenas, usando separador para separador) dividirlas, y almacenando los fragmentos en vector. Así, 19 Lenguaje awk sprintf(...) sub(expreg, nueva, cadena) gsub(expreg, nueva, cadena) substr(cadena, inicio, longitud) tolower(cadena), toupper(cadena) Jesús Alonso Abad Estrella Resa Camarero split(“30/11/2004”, v, “/”) generará los vectores v[1]=”30”, v[2]=”11”, v[3]=”2004”. Similar a printf, pero esta vez la salida generada saldrá en forma de cadena, en vez de ser mostrada por pantalla. Sustituye la subcadena más larga y más a la izquierda que concuerde con expreg dentro de cadena por nueva. cadena debe ser una variable (no sirven cadenas literales). Si se omite cadena, utilizará $0. Se puede usar el caracter especial ‘&’ si se quiere incluir de nuevo la subcadena encontrada (sustituirá ‘&’ en nueva por la subcadena). Si se quiere incluir un caracter ‘&’ dentro de la salida en vez de sustituirle por la subcadena, hay que poner una barra invertida antes (para poner esta barra invertida, es necesario ponerla como caracter literal). sub(/yo/, “mis amigos y &”) convertirá “Sólo yo” en “Sólo mis amigos y yo” sub(/y/, “\\&”) convertirá “Tú y él” en “Tú & él” Similar a sub(), pero se sustituirán todas las subcadenas que concuerden con expreg Devuelve la subcadena de cadena que comience en inicio con tamaño longitud. Si se omite longitud, devuelve el sufijo de cadena que comience en inicio. La posición de la primera letra es 1. Así, substr(cadena, 1) devuelve cadena Convierte cadena a minúsculas y mayúsculas, respectivamente. Entrada/salida: system(comando_de_sistema) Ejecuta el comando de sistema. Ejemplo: system(“ls – l”). Retorna el valor devuelto por el programa ejecutado. 8. Arrays multidimensionales Awk es capaz de trabajar con arrays multidimensionales (por ejemplo, tablas), convirtiéndoles a vectores. La forma de hacerlo es sencilla: awk convierte los índices a cadenas. Así, vector[5] es sinónimo de vector[“5”]. En el caso de usar varios índices (arrays multidimensinales), utiliza la variable implícita SUBSEP, convirtiendo las comas utilizadas para separar los índices en caracteres/cadenas indicados en SUBSEP. Por ejemplo, si SUBSEP=”:”, vector[3,2] será interpretado como vector[“3:2”], de manera que sea un vector unidimensional, con un identificador único. El valor por defecto de SUBSEP es “\034”, carácter no imprimible, y muy poco probable que aparezca en un programa awk o en los datos de entrada. Se hace así porque se pueden producir ambigüedades al fusionar los índices. Por ejemplo, si SUBSEP=”:”, vector[“1:2”, “3”] es el mismo elemento que vector[“1”, “2:3”], porque ambas apuntan a vector[“1:2:3”], mientras que con SUBSEP=“\034” es muy difícil que se dé este caso. 20 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero 9. Variables implícitas que proporcionan información a un programa ARGC, ARGV: los argumentos de la línea de comandos disponibles son almacenados en un array llamado ARGV. ARGC es el número de argumentos de lína de comandos presentes. ARGV está indexado desde 0 hasta ARGC-1. Ejemplo: awk ‘{ print ARGV [$1] }’ inventario-enviado Lista-BBS En este ejemplo, ARGV [0] contiene “awk”, ARGV[1] contiene“inventarioenviado”, y ARGV[2] “Lista-BBS”. El valor de ARGC es tres, uno más que el índice del último elemento de ARGV ya que el primer elemento es el cero. Dentro de un programa se pueden alterar los valores de estas dos variables. Cada vez que awk alcanza el final de un fichero de entrada, utiliza el siguiente argumento de ARGV como el nombre del siguiente fichero de entrada. Almacenando una cadena distinta es la variable, se pueden cambiar los ficheros que van ser leídos por el programa. Almacenando elementos adicionales e incrementado ARGC podríamos leer ficheros adicionales. Si decrementamos el valor de ARGC, eliminaremos ficheros de entrada a procesar. Si no queremos leer un fichero que se encuentra a la mitad de la lista de ficheros a procesar, almacenaremos la cadena nula (“”) en ARGV en lugar de ese nombre de fichero. De esta forma, awk le ignorará. ENVIRON: array que contiene las variables de entorno. Los índices del array son los nombres de las variables de entorno. Por ejemplo, ENVIRON{“HOME”} podría ser ‘/home/dwadmin’. El cambio de valores de este array no afecta para nada al valor real de las variables de entorno del sistema operativo. Si el sistema operativo no tuviera variables de entorno, este array estará vacío. FILENAME: nombre del fichero que awk está leyendo en la actualidad. Si awk está leyendo de la entrada estándar, esta variable toma el valor de “-“. RLENGTH: es la longitud de la cadena encontrada por la función match (explicada en el apartado de funciones implícitas) . Su valor es la longitud de la cadena encontrada o por defecto -1 si no encontró ninguna cadena. RSTART: índice de comienzo de la subcadena encontrada por la función match. Su valor es la posición de la cadena donde comienza la subcadena o 0 si no se encontró. 10. Precedencia y anidación de operadores en awk En este capítulo describiremos cuáles son los operadores que se utilizan en awk, así como el uso su orden de precedencia. Hay varios tipos de operadores en awk: están los puramente aritméticos (suma, resta, módulo…), los operadores lógicos, los operadores relacionales, los operadores de patrón, incremento… La precedencia de operadores determina cómo se agrupan los mismos cuando aparecen distintos operadores cerca unos de otros en una expresión. Si los operadores son unarios, la preferencia no importa, ya que afectan sólo a un único elemento. Por ejemplo, si tenemos $++i, es lo mismo que $(++i). Sin embargo, si 21 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero otro operador sigue al unario, se pueden dar problemas, por lo que –x**2 significa –(x**2) y no (-x)**2, ya que el operador sustracción o resta tiene menor precedencia que el operador de la multiplicación. De todas formas, la precedencia de operadores se puede cambiar mediante los paréntesis. De hecho se recomienda el uso de paréntesis cuando se tiene una combinación inusual de operadores para que la secuencia de operaciones sea más fácil de entender para los usuarios. Si se usan juntos operadores de igual precedencia, el que esté situado más a la izquierda es el que tiene mayor predecencia. Sin embargo, en el caso de los operadores asignación, exponenciación y condicionales se agrupan en el orden contrario. Ejemplos: a- b +c (a- b) +c a=b=c a = (b = c) A continuación presentamos una lista de los operadores de awk en orden de precedencia creciente: Asignación Condicional “o” lógico “y” lógico Pertenencia a array Concordancia o patrón Relacional y direccionamiento Concatenación Adición, sustracción Multiplicación, división y módulo Más y menos unario, operador not Exponenciación Incremento, decremento Campo ‘=’,’+ =’,’- =’,’* =’,’/=’, ‘% =’, ‘^ =’, ‘** =’ Estos operadores se agrupan de derecha a izquierda. ‘?:’ Estos operadores se agrupan de derecha a izquierda ‘||‘ ‘ &&’ in ‘ ~’ , ‘! ~’ Operadores relacionales: ‘<’, ‘>’, ‘<=’, ‘>=’, ‘= =’, ‘!=’ Operadores de redireccionamiento entrada-salida: ‘<’,’>’,’>>’,’|’. Los operadores relacionales y las redirecciones tienen el mismo nivel de precedencia. No se usa ningún token especial para indicar esta operación. ‘+’, ‘-‘ ‘*’, ‘/’, ‘%’ ‘+’,’-‘,’!’ ‘^’,’**’ Estos operadores se agrupan de derecha a izquierda ‘+ +’, ‘--‘ ‘$’ 11. Portabilidad: awk compilado Existen numerosas herramientas de conversión y de compilación de awk, que permiten un mejor rendimiento y portabilidad, en el sentido de que deja de ser necesario tener instalado awk en la máquina. Ejemplos de estas herramientas son awka 22 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero (http://awka.sourceforge.net/), proyecto open-source que genera código fuente ANSI-C a partir de un script awk, que es posteriormente compilado por gcc o cualquier otro compilador de lenguaje C, o la herramienta comercial awkc (http://www.mkssoftware.com/docs/man1/awkc.1.asp), que compila directamente el código fuente de un script awk. Existen muchas más herramientas de este tipo disponibles en la red. 23 Lenguaje awk Jesús Alonso Abad Estrella Resa Camarero 12. Bibliografía y referencias Sed & Awk. Por Dale Dougherty & Arnold Robbins. O’Reilly. 1997 Manual de awk. Por Jesús Alberto Vidal Cortés. Edición del 2002 http://inicia.es/de/chube/Manual_Awk/Menus.htm Ejemplos con awk: una breve introducción. Por Jesús Palacios Bermejo http://www.linuxfocus.org/Castellano/September1999/article103.html Resumen del lenguaje awk http://club.telepolis.com/jagar1/Unix/Awk.htm Curso de awk Rodolfo Pilas (apuntes de una conferencia impartida por Heber Godoy) http://www.linux.org.uy/uylug/cursos/awk/awk.html awk – data transformation, report generation language http://www.mkssoftware.com/docs/man1/awk.1.asp Introduction to awk. Por Brian Brown, CIT http://allman.rhon.itam.mx/dcomp/awk.html Awk language programming http://snap.nlc.dcccd.edu/reference/awkref/gawk_toc.html Sección sobre Awk en “La web del programador” http://www.lawebdelprogramador.com/cursos/mostrar.php?id=174&texto=AWK 24