Esta página puede ser redistribuida libremente bajo los términos de la licencia GPL. Vease ( GPL texto original ) o si lo prefiere (Traducción española no oficial de la GPL) Al margen de las obligaciones legales que se derivan del uso de esta licencia rogamos sea respetada la referencia a su lugar de publicación original www.ciberdroide.com. y a su autor original Antonio Castro Snurmacher (Madrid 01/01/2000). |
![]() |
Esta ausencia de garantía se hace extensa a cualquier tipo de uso de este material y muy especialmente a las prácticas, ejercicios, y de ejemplos que encuentre en estas páginas. Deberá trabajar siempre salvo indicación contraria con un SO Linux y con un usario distinto de 'root' sin privilegios especiales. Como directorio de trabajo se procurará usar el directorio '/tmp' o algún otro que no contenga información valiosa. Tampoco se considera buena idea practicar en una máquina que contenga información valiosa. Todo esto son recomendaciones de prudencia. En cualquier caso si algo sale mal toda la responsabilidad será únicamente suya. En ningún caso podrá reclamar a nadie por daños y perjuicios derivados del uso de este material. Para más información lea el contenido de la licencia GPL y abstengase de hacer prácticas si no está dispuesto a asumir toda la responsabilidad.
|
Que es awk y para que se usa
Dado que los SO tipo Unix incluido Linux acostumbran con mucha frecuencia
a usar ficheros de configuración del sistema en formatos de de texto
perfectamente legibles y editables se diseño un lenguaje para poder
procesar este tipo de ficheros de datos en formato de texto.
Cuando un programa necesita una velocidad muy alta para acceder a unos
datos o para modificarlos se utilizan estructuras de datos más sofisticadas.
En muchos otros casos un fichero de configuración será accedido de forma
muy ocasional, y resulta más interesante usar un formato de texto sencillo.
Por ejemplo hay ficheros de configuración que solo se usan durante la carga
de un programa. Algunos de estos programas suelen cargarse una sola vez
mientras arranca el sistema y luego en condiciones normales permanecen
arrancados todo el tiempo.
'awk' nació en 1978 como un lenguaje pequeño y sencillo pero desde entonces
ha evolucionado mucho y en la actualidad se puede afirmar que es un lenguaje
muy potente y versátil. Imposible tratarlo en profundidad en un curso como este.
'awk' es un complemento muy bueno para su uso con shell-script. Esta lección
no va a condicionar la asimilación de lecciones posteriores pero recomendamos
que como mínimo le de un breve repaso ya que 'awk' puede resultar extremadamente
útil en determinadas circunstancias.
Nos vamos a conformar con explicar unas pocas cosas porque con ello
conseguiremos dos objetivos. El primero que pueda usarlo para un limitado
tipo de tareas bastante frecuentes, y el segundo que conozca su existencia
y para que se usa. De esta forma puede ampliar conocimientos por su cuenta
cuando lo necesite.
Nos vamos a centrar en el procesamiento de datos en los cuales cada línea
estará estructurada en campos. Estos campos estarán delimitados entre si por
algún carácter o por alguna secuencia especial de caracteres especialmente
reservado para ello. Esta secuencia será el delimitador de campos y no debe
aparecer en el interior de ningún campo. Cada línea equivale a un registro.
La mayoría de las bases de datos, y hojas de cálculo permiten volcar
los datos en formato de texto para poder ser exportados entre distintas
bases de datos. Estas salidas se pueden procesar fácilmente mediante 'awk'.
También se puede usar 'awk' con la salida de diversos programas. Esto
permite entre otras cosas usar 'awk' para acoplar una salida de un programa
con la entrada de otro que necesite un formato muy distinto. En definitiva
vamos a explicar solo una pequeña parte de este potente lenguaje pero
comprobará su gran utilidad muy pronto.
Forma de uso
Se puede usar de varias formas. Tenemos que pasar a 'awk' el texto del
programa, y los datos. El primero se puede pasar bien como argumento
o indicando -f nombre del fichero que contiene el texto del programa.
La entrada se puede pasar dando el nombre del fichero de entrada como
último argumento o en caso contrario lo tomará por la entrada estándar.
El programa que acabamos de utilizar imprimirá el literal
"Hola mundo" a cada línea de datos que procese. En este caso
usamos solo un par de líneas vacías como entrada de datos.
Vamos a localizar el binario de 'awk'
Vamos a suponer que en su sistema se encuentre también en '/usr/bin'.
Puesto que awk es un lenguaje interpretado perfectamente legible
también podemos decir que los programas de awk son script. Para poder
usarlos directamente podemos añadir una primera línea con número mágico
y poner permiso de ejecución.
Estructura de un programa awk
El primer ejemplo que vimos anteriormente ("Hola mundo") solo tenía
una de las tres partes.
Concretamente era la parte central ya que no pusimos ninguna de las
palabras reservadas BEGIN o END.
Vamos a poner ahora un ejemplo con las tres partes.
Edite un fichero con nombre '/tmp/3partes.awk'
Ejecutelo con:
Es importante que comprenda que la parte central se ejecutará
tantas veces como líneas de datos existan. En nuestro ejemplo son
cuatro líneas generadas por 'echo -e "\n\n\n" '. En cambio las partes
'BEGIN { ... }' y 'END { ... }' se ejecutan una sola vez. La primera
antes de procesar la primera línea y la última después de procesar la
última línea.
Los comentarios en 'awk' comienzan con un '#' y terminan al final de
la línea.
Expresiones regulares
Ahora editamos un segundo fichero '/tmp/expreg.dat':
Ahora ejecute lo siguiente:
Vemos que cada línea de datos puede cumplir más de una regla y
que cuando no ponemos una expresión regular siempre se ejecutará
la acción. En este caso todas las líneas provocan la escritura de
una línea de guiones '--------------------------------------'.
El uso de expresiones regulares puede ayudarnos a eliminar cabeceras,
líneas vacías o incompletas o cosas así que no deseamos procesar.
Delimitadores de campos
Lo mismo para definir un delimitador que en cualquier otro caso
donde se usen cadenas de caracteres podemos encontrarnos la necesidad
de usar caracteres especiales que no pueden ser introducidos directamente.
Para ello existen determinadas secuencias que empiezan por el carácter
'\' y que tienen significado especial.
El último caso se usa para eliminar el significado especial de un carácter
en determinadas circunstancias. Por ejemplo para usar un '+' o un '-' en
una expresión regular usaríamos '\+' o '\-'
Podemos elegir un solo carácter para separar campos.
Hay ficheros de configuración como /etc/passwd, /etc/group, que
usan un solo carácter para delimitar los campos. Por ejemplo los dos
puntos ':' , el blanco '\ ', la coma ',' el tabulador '\t' etc...
'awk' permite usar como delimitador más de un carácter. Para ello se
asignará a la variable 'FS' una cadena de caracteres que contenga
una expresión regular . Por ejemplo para usar como delimitador
el carácter ':' habría que incluir 'BEGIN { FS = ":" }'
Si no se especifica ningún delimitador se asumirá que los campos
estarán delimitados por uno o más blancos o tabuladores consecutivos
lo cual se expresa como "[\ \t]+". Las expresiones regulares ya fueron
estudiadas en un capítulo especial. El carácter '\' debe
usarse para escapar cualquier carácter con significado especial en una
expresión regular y algunos caracteres normales precedidos de '\' se
usan para representar caracteres especiales. '\t' es el tabulador.
En 'awk' se usa $1 para referenciar el campo 1, $2 para referenciar el campo 2,
etc... y para referenciar el registro completo usaremos $0.
Edite el siguiente fichero '/tmp/delim1.awk'
$1, $2, $3, y $4 representan a los campos 1, 2, 3, y 4 respectivamente.
Edite el siguiente fichero de datos '/tmp/delim1.dat'
En la primera línea debe introducir un blanco para separar los primeros
blancos y una secuenciencia de '
Ahora ejecute lo siguiente:
Edite el siguiente fichero '/tmp/delim0.awk'
Ahora ejecute lo siguiente:
Con ello hemos conseguido variar el orden de aparición de
los campos, pero todavía no hemos especificado ningún delimitador.
Por ello hemos asumido el delimitador por defecto. (uno o más blancos
y tabuladores). Para especificar un delimitador distinto tenemos
que asignar su valor a la variable FS y además tenemos que hacerlo
antes de leer el primero registro por lo cual se incluirá la instrucción
en la sección inicial precedida de BEGIN.
Edite el siguiente fichero '/tmp/delim2.awk'
Estamos definiendo un único carácter blanco como separador.
Ahora ejecute lo siguiente:
Vamos a cambiar de delimitador.
Edite el siguiente fichero '/tmp/delim3.awk'
Estamos definiendo un único carácter tabulador como separador.
Ahora ejecute lo siguiente:
Selección de registros por campo
Lo primero que vemos es que tiene una cabecera de dos líneas inútiles
y un final también inútil. Podemos asegurar que las líneas que deseamos
procesar cumplirán un patrón de dos números guión dos números guión
cuatro números y línea vertical. Vamos a editar un programa que llamaremos
'contabil1.awk'
Vamos a ejecutar este ejemplo y vamos a ver su salida
Podemos apreciar varias cosas. NR es una variable del sistema
que toma el valor del número de registro que se está procesando.
Podemos ver que las dos primeras líneas y la última han sido
descartadas. También vemos que las primeras líneas usan un solo
dígito para el número de registro y luego usan dos dígitos. Esto
que las columnas no queden alineadas.
Vamos a modificar el programa para que muestre los registros completos
($0) cuando no se cumpla la condición anterior. Para ello editaremos un
fichero que llamaremos 'contabdescarte.awk'.
Vamos a ejecutar este ejemplo y vamos a ver su salida
Formato de salida con printf
Hay que tener en cuenta que en 'awk' la concatenación de cadenas se
usa poniendo una cadena a continuación de otra separada por blancos.
Por ejemplo:
En estos formatos se puede intercalar inmediatamente a continuación del
'%' primero un signo '+' o un '-' que son opcionales y significan
respectivamente alineación a la derecha o a la izquierda. En segundo lugar
y de forma igualmente opcional se puede intercalar un número para indicar
un ancho mínimo. (Si el dato ocupa más que el dato especificado se muestra
el dato completo haciendo caso omiso de la indicación de anchura). En el
caso de coma flotante se puede indicar el ancho total del campo y la precisión
(anchura) de la parte decimal.
Veamos unos pocos ejemplos.
Practique un poco investigando con más detalle el funcionamiento de estos
formatos.
Uso de variables operadores y expresiones
Vamos a editar un fichero que llamaremos 'ejemplexpr.awk' con algunas
expresiones aritméticas.
Lo ejecutamos y observamos el resultado.
No podemos explicar en detalle todo el lenguaje 'awk'. Se trata de que
comprenda su utilidad y de que sea capaz de utilizarlo para determinadas
tareas en las cuales resulta extremadamente útil.
Algunas expresiones parecen inspiradas en el lenguaje C. Otras parece
que han servido de inspiración para el lenguaje Perl. En realidad muchos
lenguajes usan expresiones parecidas.
Por ello vamos a resumir en forma de tabla una serie de elementos que
intervienen en las expresiones que 'awk' es capaz de manejar. Pero no
vamos a explicar en detalle cada cosa. En lugar de eso daremos una
descripción resumida y procuraremos que los ejemplos posteriores tengan
un poco de todo.
Computo con registros
Vamos a modificar el programa 'contabil1.awk' para procesar solo los
registros de
consumo de luz, vamos a mejorar el formato de salida, vamos a incluir
un contador de registros seleccionados, un contador de consumo de luz,
y al final obtendremos el consumo total y el consumo promedio de luz.
Lo llamaremos 'contabil2.awk'
Vamos a ejecutar este ejemplo y vamos a ver su salida
Los datos que estamos usando para el ejemplo están ordenados por fechas.
Vamos a obtener un informe con un campo más que será el saldo de la cuenta.
Para ello editamos un fichero que llamaremos 'contabil3.awk'.
Vamos a ejecutar este ejemplo y vamos a ver su salida
Sentencias condicionales y bucles
'awk' es un lenguaje muy completo y no podía faltar las sentencias
de ejecución condicional y de ejecución en bucle.
Algunos de los conceptos que vamos a comentar ya los hemos visto cuando
hablamos de la programación en bash y no vamos a explicar con demasiado
detalle cada tipo de sentencia. La sintaxis que usa awk no se parece a
la sintaxis que ya hemos visto para bash. Se parece más a la sintaxis
del lenguaje C. De todas formas los conceptos ya nos resultan familiares
y usaremos algunos ejemplos para ilustrarlos.
Empezaremos describiendo la sintaxis de cada tipo de sentencia. Denominaremos
acción a una sentencia simple o a una sentencia compuesta de la forma
'{ sentencia1 ; sentencia2 ; ... }'
Sentencia condicional con los operadores '?' y ':'
Bucle 'while'
Bucle 'do' 'while'
Bucle 'for'
Veremos de momento tan solo un ejemplo para la sentencia
condicional 'if'.
Edite el siguiente fichero que llamaremos 'contabil4.awk'
Vamos a ejecutar este ejemplo y vamos a ver su salida
Pasar valores al script awk
Edite el siguiente fichero que llamaremos 'contabil5.awk'
Vamos a ejecutar este ejemplo pasando y vamos a ver su salida
Hay que advertir que el paso de parámetros equivale a definir una
variable y a asignar un valor pero esto valor no será accesible
hasta después de leer el primer registro. Si el valor pasado como
parámetro tuviera que ser accesible en la sección BEGIN habría que
usar la opción -v previo al paso del parámetro.
Repetiremos el ejemplo pasando el delimitador del registro que usaremos
en la sección BEGIN.
Edite el siguiente fichero que llamaremos 'contabil6.awk'
Vamos a ejecutar este ejemplo pasando valores y vamos a ver su salida
Vemos que no hemos obtenido el resultado esperado.
Vamos a volver a ejecutar este ejemplo pasando el valor del delimitador con
la opción -v y vamos a ver su nueva salida
Declaración de funciones
La sintaxis es muy sencilla.
Por ejemplo para declarar una función que retorne un número aleatorio
entre 1 y 6.
Edite un fichero con nombre 'dado.awk'.
Ahora lo ejecutamos
Función system
Por ejemplo si quisiéramos verificar la existencia de un fichero
almacenado en la variable 'nombre_fich' tendríamos que hacer
La función getline y otras funciones avanzadas
En primer lugar hay que advertir que 'getline' que al igual que otras
funciones devuelve un valor pero su sintaxis no es una típica
sintaxis de función. No se usa como 'getline()' sino como una sentencia.
Esta función retorna 1 si lee una línea, 0 si alcanza el fin de la entrada
de datos y -1 si se produce un error.
Usada simplemente como 'getline' sin nada más lee la siguiente linea de
la entrada asignando $0 y desglosando los campos en $1, $2, $3, etc..
Se puede asignar el valor completo de la línea leída a una variable con
'getline variable' evitando de esta forma alterar el valor de $0.
Se puede leer de un fichero usando el operador redirección.
'getline < "fichero"'. Se puede simbolizar la entrada estándar como "-"
Se puede leer desde un pipe. '"whoami" | getline usuario'.
Edite un fichero llamado 'tipo_usuario.awk'.
Ejecute lo con
No pretendemos con este sencillo ejemplo que sea capaz de usar estas
funciones.
El manejo de estas redirecciones es complicado y en ocasiones se hace
necesario forzar el cierre de una entrada o de un pipe. Para eso existe
la función 'close'. Se usa haciendo 'close ("fichero")' o 'close ("whoami")'.
Por el momento nos conformamos con lo explicado y veremos un poco más
adelante el uso de getline tomando la entrada de un fichero cuando
expliquemos los arrays asociativos.
Arrays
Vamos a usar awk para procesar la salida obtenida con 'ps'. Primero
vamos a suponer que obtenemos un listado completo de los procesos del
sistema en formato largo. Si intenta realizar este ejemplo obtendrá
un resultado necesariamente diferente.
Para dar una idea de la situación de parentescos entre los distintos
procesos mostramos la salida obtenida con el comando 'pstree' ejecutado
desde la misma sesión de xterm que en el caso anterior.
Solo vamos a procesar los campos PID y PPID. $3 y $4 respectivamente.
Los meteremos en un par de arrays llamados pid y ppid.
Ahora ejecutamos pasando el pid del proceso del cual deseamos averiguar
su descendencia.
Con un número realmente reducido de líneas de código acabamos de
procesar la salida de un comando que no estaba especialmente diseñado
para ser procesado sino para entregar un resultado legible.
No se emocione todavía porque solo hemos utilizado los arrays con indices
numéricos. Lo cierto es que los arrays de 'awk' a diferencia de los arrays
de otros lenguajes son arrays asociativos. Eso significa que podemos usar
como índice una cadena de caracteres. Por ejemplo podemos hacer lo siguiente:
nombre_cli["5143287H"]="Luis, García Toledano"
No es necesario dar un tamaño al
array. Un array asociativo no establece un orden entre sus elementos.
Hay que aclarar que el manejo de un array con índices numéricos corresponde
a un mecanismo muy simple ya que se usan porciones consecutivas de memoria
del ordenador y se accede directamente por posición. Por el contrario un
array asociativo de las características de 'awk' se va creando dinámicamente.
Internamente 'awk' gestiona el acceso mediante una técnica de hash que usa
tablas auxiliares a modo de tablas de índices y funciones auxiliares que
obtiene valores numéricos a partir de valores de una cadena. Todo ello
permite un acceso muy rápido en este tipo de estructuras haciéndolas
adecuadas para su uso en bases de datos.
El programa que acabamos de realizar ilustra la potencia de 'awk' para
el tratamiento de ficheros de datos. Si nuestro fichero de datos de ejemplo
'articulos.dat' tuviera un número de registros mucho mayor habríamos
notado que inicialmente se tarda un cierto tiempo en leer todo el fichero
de datos pero una vez almacenados los datos en el array su acceso a los
mismos es rapidísimo. Esta rápidez se debe no solo a que los datos ya han
sido leidos desde el disco duro y ya están en memoria sino porque toda
la información está indexada de forma que la localizacion de cualquier
elemento del array es muy rápida.
Faltan muchas cosas por contar sobre 'awk'. Si dispone de algún fichero
de datos de interes personal saque una copia e intente realizar alguna
utilidad en 'awk'.
La palabra 'awk' se usa tanto para referirse a un lenguaje de manipulación
de ficheros de datos como para referirse a su interprete.
'awk' suele estar instalado en la mayoría de los sistemas ya que su
uso suele ser necesario. Por eso en Linux suele encontrarse entre los
paquetes básicos del sistema en todas las distribuciones.
$ ## Generamos en /tmp un par de ficheros
$ echo -e "\n" > /tmp/echo.out
$ echo '{ print "Hola mundo" }' > /tmp/ejemplo1.awk
$ ## Ejecutaremos el mismo programa de 4 formas distintas
$ echo -e "\n" | awk '{ print "Hola mundo" }'
Hola mundo
Hola mundo
$ awk '{ print "Hola mundo" }' /tmp/echo.out
Hola mundo
Hola mundo
$ echo -e "\n" | awk -f /tmp/ejemplo1.awk
Hola mundo
Hola mundo
$ awk -f /tmp/ejemplo1.awk /tmp/echo.out
Hola mundo
Hola mundo
$ whereis awk
/usr/bin/awk
$ echo '#!/usr/bin/awk -f' > /tmp/ejemplo2.awk
$ echo '{ print "Hola mundo" }' >> /tmp/ejemplo2.awk
$ chmod +x /tmp/ejemplo2.awk
$ echo -e "\n" | /tmp/ejemplo2.awk
Hola mundo
Hola mundo
Un programa 'awk' puede tener tres secciones distintas.
BEGIN { print "Erase una vez..." }
{ print "...y entonces bla, bla, bla ..." }
END { print "...y colorín colorado este cuento se ha acabado." }
$ echo -e "\n\n\n" | awk -f /tmp/3partes.awk
çAma Erase una vez...
...y entonces bla, bla, bla ...
...y entonces bla, bla, bla ...
...y entonces bla, bla, bla ...
...y entonces bla, bla, bla ...
...y colorín colorado este cuento se ha acabado.
Algunas veces los datos pueden venir con algunas lineas que no interesa
procesar o que se deben procesar de forma distinta. Podemos usar una
expresión regular delimitada por el carácter '/' para seleccionar una
acción especial.
Vamos a editar otro ejemplo que llamaremos '/tmp/expreg.awk':
BEGIN { print "Erase una vez..." }
/^$/ { print "Linea vacía" }
/[0-9]+/ { print "Tiene un número" }
/\.$/ { print "Termina con punto" }
# Esto es un comentario
{ print "--------------------------------------" }
END { print "...y colorín colorado este cuento se ha acabado." }
Línea número 1.
Línea número 2
....
Fin de los datos
$ awk -f /tmp/expreg.awk /tmp/expreg.dat
Erase una vez...
Tiene un número
Termina con punto
--------------------------------------
Tiene un número
--------------------------------------
Linea vacía
--------------------------------------
Termina con punto
--------------------------------------
--------------------------------------
...y colorín colorado este cuento se ha acabado.
No hemos tratado aun los campos de una línea. Una línea que tenga
distintos campos debe usar alguna secuencia para delimitar los
campos entre si.
\a Produce un pitido en el terminal \b Retroceso \f Salto de página \n Salto de línea \r Retorno de carro \t Tabulador horizontal \v Tabulador vertical \ddd Carácter representado en octal por 'ddd' \xhex Carácter representado en hexadecimal por 'hex' \c Carácter 'c'
{ print "+", $1, "+", $2, "+", $3, "+", $4, "+" }
aaa bbb ccc ddd eee
111 222 333 444
$ awk -f /tmp/delim1.awk /tmp/delim1.dat
+ aaa + bbb + ccc + ddd +
+ 111 + 222 + 333 + 444 +
{ print "+", $3, "+", $4, "+", $1, "+", $2, "+" }
$ awk -f /tmp/delim0.awk /tmp/delim1.dat
+ ccc + ddd + aaa + bbb +
+ 333 + 444 + 111 + 222 +
BEGIN { FS = "\ " }
{ print "+", $1, "+", $2, "+", $3, "+", $4, "+" }
$ awk -f /tmp/delim2.awk /tmp/delim1.dat
+ aaa + bbb + ccc + +
+ 111 + 222 + 333 + 444 +
BEGIN { FS = "\t" }
{ print "+", $1, "+", $2, "+", $3, "+", $4, "+" }
$ awk -f /tmp/delim3.awk /tmp/delim1.dat
+ aaa bbb ccc + + ddd + +
+ 111 222 333 444 + + + +
Vamos a editar un fichero que simulará la salida de datos obtenida
desde una base de datos relacional. Usaremos estos datos en varios
ejemplos. Puede corresponder a una contabilidad de un alquiler de
un piso. Lo llamaremos 'contabil.dat'.
fecha|concepto|importe
----------+--------+-------
01-01-1999|- | 96
16-12-1999|AGUA | -14650
05-01-2000|LUZ | -15797
24-01-2000|GAS | -34175
27-01-2000|INGRESO | 141200
01-02-2000|MENS | -96092
25-02-2000|LUZ | -12475
01-03-2000|MENS | -96092
06-03-2000|INGRESO | 101300
01-04-2000|MENS | -96092
06-04-2000|AGUA | -15859
07-04-2000|INGRESO | 134000
01-05-2000|MENS | -98975
02-05-2000|LUZ | -11449
09-05-2000|INGRESO | 95000
23-05-2000|GAS | -21428
25-05-2000|GAS | -16452
01-06-2000|MENS | -98975
07-06-2000|INGRESO | 130000
01-07-2000|MENS | -98975
04-07-2000|LUZ | -12403
07-07-2000|AGUA | -5561
10-07-2000|INGRESO | 99000
24-07-2000|GAS | -11948
01-08-2000|MENS | -98975
10-08-2000|INGRESO | 122355
04-09-2000|LUZ | -12168
10-09-2000|INGRESO | 129000
19-09-2000|AGUA | -10529
28-09-2000|GAS | -2620
01-10-2000|MENS | -98975
10-10-2000|INGRESO | 112000
(32 rows)
BEGIN { FS="\|" }
/[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]\|/ {
print NR, ", ", $1, ", ", $2, ", ", $3
}
$ awk -f contabil1.awk < contabil.dat
3 , 01-01-1999 , - , 96
4 , 16-12-1999 , AGUA , -14650
5 , 05-01-2000 , LUZ , -15797
6 , 24-01-2000 , GAS , -34175
7 , 27-01-2000 , INGRESO , 141200
8 , 01-02-2000 , MENS , -96092
9 , 25-02-2000 , LUZ , -12475
10 , 01-03-2000 , MENS , -96092
11 , 06-03-2000 , INGRESO , 101300
12 , 01-04-2000 , MENS , -96092
13 , 06-04-2000 , AGUA , -15859
14 , 07-04-2000 , INGRESO , 134000
15 , 01-05-2000 , MENS , -98975
16 , 02-05-2000 , LUZ , -11449
17 , 09-05-2000 , INGRESO , 95000
18 , 23-05-2000 , GAS , -21428
19 , 25-05-2000 , GAS , -16452
20 , 01-06-2000 , MENS , -98975
21 , 07-06-2000 , INGRESO , 130000
22 , 01-07-2000 , MENS , -98975
23 , 04-07-2000 , LUZ , -12403
24 , 07-07-2000 , AGUA , -5561
25 , 10-07-2000 , INGRESO , 99000
26 , 24-07-2000 , GAS , -11948
27 , 01-08-2000 , MENS , -98975
28 , 10-08-2000 , INGRESO , 122355
29 , 04-09-2000 , LUZ , -12168
30 , 10-09-2000 , INGRESO , 129000
31 , 19-09-2000 , AGUA , -10529
32 , 28-09-2000 , GAS , -2620
33 , 01-10-2000 , MENS , -98975
34 , 10-10-2000 , INGRESO , 112000
BEGIN { FS="\|" }
! /[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]\|/ {
print NR, $0
}
$ awk -f contabdescarte.awk < contabil.dat
1 fecha|concepto|importe
2 ----------+--------+-------
35 (32 rows)
Para imprimir con formato usaremos 'printf' en lugar de 'print'.
printf se usa en varios lenguajes. El primer argumento de esta
función debe de ser una cadena de caracteres que contenga el formato
de salida deseado para la salida. Los formatos de cada dato se
expresan mediante unas directivas que empiezan con el carácter
'%' y debe de existir en dicha cadena tantas directivas como datos
separados por coma a continuación de la cadena de formato.
# cad = "(unodos)"
cad = "uno" "dos" ; cad = "(" cad ")"
%c Carácter ASCII %d Entero representado en decimal %e Coma flotante (exponente = e[+-]dd) %E Coma flotante (exponente = E[+-]dd) %f Coma flotante sin exponente %g Equivale al más corto de los formatos 'e' o 'f' %G Equivale al más corto de los formatos 'E' o 'F' %o Entero representado en octal %s Cadena de caracteres %x Entero representado en hexadecimal con minúsculas %X Entero representado en hexadecimal con mayúsculas %% Carácter '%'
$ echo | awk '{ print "Hola mundo" }'
Hola mundo
$ echo | awk '{ printf "Hola %s\n", "mundo" }'
Hola mundo
$ echo | awk '{ printf "#%d#%s#\n", 77, "mundo" }'
#77#mundo#
$ echo | awk '{ printf "#%10d#%10s#\n", 77, "mundo" }'
# 77# mundo#
$ echo | awk '{ printf "#%-10d#%-10s#\n", 77, "mundo" }'
#77 #mundo #
$ echo | awk '{ printf "#%+4d#%+4s#\n", 77, "mundo" }'
# +77#mundo#
$ echo | awk '{ printf "#%04d#%+4s#\n", 77, "mundo" }'
#0077#mundo#
$ echo | awk '{ printf "#%010.5f#%E#%g\n", 21.43527923, 21.43527923, 21.43527923 }'
#0021.43528#2.143528E+01#21.4353
$ echo | awk '{ printf "#%10.5f#%E#%g\n", 2140000, 2140000, 2140000 }'
#2140000.00000#2.140000E+06#2.14e+06
En 'awk' podemos usar toda clase de expresiones presentes en
cualquier lenguaje. Cualquier identificador que no corresponda
con una palabra reservada se asumirá que es una variable. Para
asignar un valor se usa el operador '='
{
contador = 0; # Pone a cero la variable contador
contador ++; # Incrementa en 1 la variable contador
contador +=10; # Incrementa en 10 la variable contador.
contador *=2 # Multiplica por 2 la variable contador
print contador
contador = ( 10 + 20 ) / 2 ;
print contador
contador = sqrt ( 25 ) ; # Raiz cuadrada de 25
print contador
}
$ echo | awk -f ejemplexpr.awk
22
15
5
+ Suma - Resta * Multiplicación / División % Módulo (resto) ^ Potenciación var = expr Asignación var ++ Incrementa la variable en una unidad var -- Decrementa la variable en una unidad var += expr_aritm Incrementa la variable en cierta cantidad var -= expr_aritm Decrementa la variable en cierta cantidad var *= expr_aritm Multiplica la variable por cierta cantidad var /= expr_aritm Divide la variable por cierta cantidad var %= expr_aritm Guarda en la variable el resto de su división por cierta cantidad var ^= expr_aritm Eleva el valor de la variable en cierta cantidad expr_aritm == expr_aritm Comparación de igualdad expr_aritm != expr_aritm Comparación de desigualdad expr_aritm < expr_aritm Comparación menor que expr_aritm > expr_aritm Comparación mayor que expr_aritm <= expr_aritm Comparación menor igual que expr_aritm >= expr_aritm Comparación mayor igual que expr_cad ~ expr_regular Se ajusta al patrón expr_cad !~ expr_regular No se ajusta al patrón expr_logica || expr_logica Operador lógico AND (Y) expr_logica && expr_logica Operador lógico OR (O) ! expr_logica Operador lógico NOT (NO) atan2( y, x) Retorna el arco-tangente de y/x en radianes cos(x) Retorna el coseno de x en radianes exp(x) Retorna el exponencial de x (e^x) int(x) Retorna el valor entero de x truncado la parte decimal log(x) Retorna el logaritmo neperiano de x rand() Retorna un valor seudo aleatorio comprendido entre 0 y 1 sin(x) Retorna el seno de x en radianes sqrt(x) Retorna la raiz cuadrada de x srand(x) Inicializa la semilla para generar números pseudoaleatorios gsub(r, s, t)
Sustituye 's' globalmente en todo 't' cada vez que se encuentre un
patrón ajustado a la expresión regular 'r'. Si no se proporciona 't'
se toma $0 por defecto.
Devuelve el número de sustituciones realizado. index(cadena, subcadena) Retorna la posición de la 'subcadena' en 'cadena' (Primera posición = 1) length(cadena)
Devuelve la longitud de la 'cadena'. Tomará $0 por defecto si no se
proporciona 'cadena' split(cadena, array, sep)
Parte 'cadena' en elementos de 'array' utilizando 'sep' como separador.
Si no se proporciona 'sep' se usará FS. Devuelve el número de elementos
del array sub(r, s, t)
Sustituye 's' en 't' la primera vez que se encuentre un patrón
ajustado a la expresión regular 'r'. Si no se proporciona 't' se toma $0
por defecto.
Devuelve 1 si tiene éxito y 0 si falla substr(cadena, beg, len)
Devuelve una subcadena de 'cadena' que empieza en 'beg' con una longitud
'len'. Si no se proporciona longitud devuelve hasta el final de la cadena
desde 'beg' tolower(cadena) Pasa a minúsculas toupper(cadena) Pasa a mayúsculas match(cadena, expr_reg) Indica si 'cadena' se ajusta o no a la expresión regular 'expr_reg' system(comando) sprintf(formato [, expr-list] ) Para obtener salida con formato.
BEGIN {
FS="\|" ;
cont_reg_luz=0;
cont_importe_luz=0;
}
$2 ~ /LUZ/ {
cont_reg_luz = cont_reg_luz + 1 ;
cont_importe_luz = cont_importe_luz + $3 ;
printf ("%3d, %3d, %s, %s, %s, %10d\n", NR, cont_reg_luz, $1, $2, $3, cont_importe_luz);
}
END {
printf ("Consumo promedio = %d\n", cont_importe_luz / cont_reg_luz) ;
}
$ awk -f contabil2.awk < contabil.dat
5, 1, 05-01-2000, LUZ , -15797, -15797
9, 2, 25-02-2000, LUZ , -12475, -28272
16, 3, 02-05-2000, LUZ , -11449, -39721
23, 4, 04-07-2000, LUZ , -12403, -52124
29, 5, 04-09-2000, LUZ , -12168, -64292
Consumo promedio = -12858
BEGIN {
FS="\|" ;
cont_importe=0;
}
/[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]\|/ {
cont_importe = cont_importe + $3 ;
printf ("%3d, %s, %s, %s, %10d\n", NR, $1, $2, $3, cont_importe);
}
$ awk -f contabil3.awk < contabil.dat
3, 01-01-1999, - , 96, 96
4, 16-12-1999, AGUA , -14650, -14554
5, 05-01-2000, LUZ , -15797, -30351
6, 24-01-2000, GAS , -34175, -64526
7, 27-01-2000, INGRESO , 141200, 76674
8, 01-02-2000, MENS , -96092, -19418
9, 25-02-2000, LUZ , -12475, -31893
10, 01-03-2000, MENS , -96092, -127985
11, 06-03-2000, INGRESO , 101300, -26685
12, 01-04-2000, MENS , -96092, -122777
13, 06-04-2000, AGUA , -15859, -138636
14, 07-04-2000, INGRESO , 134000, -4636
15, 01-05-2000, MENS , -98975, -103611
16, 02-05-2000, LUZ , -11449, -115060
17, 09-05-2000, INGRESO , 95000, -20060
18, 23-05-2000, GAS , -21428, -41488
19, 25-05-2000, GAS , -16452, -57940
20, 01-06-2000, MENS , -98975, -156915
21, 07-06-2000, INGRESO , 130000, -26915
22, 01-07-2000, MENS , -98975, -125890
23, 04-07-2000, LUZ , -12403, -138293
24, 07-07-2000, AGUA , -5561, -143854
25, 10-07-2000, INGRESO , 99000, -44854
26, 24-07-2000, GAS , -11948, -56802
27, 01-08-2000, MENS , -98975, -155777
28, 10-08-2000, INGRESO , 122355, -33422
29, 04-09-2000, LUZ , -12168, -45590
30, 10-09-2000, INGRESO , 129000, 83410
31, 19-09-2000, AGUA , -10529, 72881
32, 28-09-2000, GAS , -2620, 70261
33, 01-10-2000, MENS , -98975, -28714
34, 10-10-2000, INGRESO , 112000, 83286
Sentencia condicional 'if'
Dentro de los bucles podemos usar break para forzar la salida de un
bucle o continue para saltar a la siguiente iteración.
if ( expresión_lógica )
accion1
[ else
accion2 ]
expresion_lógica ? accion1 : accion2
while ( expresión_lógica )
accion
do
accion
while ( expresión_lógica )
for ( inicializar_contador ; comprobar_contador ; modificar_contador )
accion
BEGIN { FS="\|" ; }
/[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]\|/ {
if ( $3 >= 0) {
printf ("%3d, %s, %s, %s\n", NR, $1, $2, $3);
}
}
$ awk -f contabil4.awk < contabil.dat
3, 01-01-1999, - , 96
7, 27-01-2000, INGRESO , 141200
11, 06-03-2000, INGRESO , 101300
14, 07-04-2000, INGRESO , 134000
17, 09-05-2000, INGRESO , 95000
21, 07-06-2000, INGRESO , 130000
25, 10-07-2000, INGRESO , 99000
28, 10-08-2000, INGRESO , 122355
30, 10-09-2000, INGRESO , 129000
34, 10-10-2000, INGRESO , 112000
En ocasiones puede resultar interesante poder pasar algún valor
al script awk. Vamos a modificar el programa anterior para que
muestre los registros con un importe superior a un valor que
pasaremos por parámetro.
BEGIN { FS="\|" ; }
/[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]\|/ {
if ( $3 >= minimo && $3 <= maximo ) {
printf ("%3d, %s, %s, %s\n", NR, $1, $2, $3);
}
}
$ awk -f contabil5.awk minimo=100000 maximo=120000 < contabil.dat
11, 06-03-2000, INGRESO , 101300
34, 10-10-2000, INGRESO , 112000
BEGIN { FS = delimitador ; }
/[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]\|/ {
if ( $3 >= minimo && $3 <= maximo ) {
printf ("%3d, %s, %s, %s\n", NR, $1, $2, $3);
}
}
$ awk -f contabil6.awk minimo=100000 maximo=120000 deliminador='|' < contabil.dat
$ awk -v delimitador='|' -f contabil6.awk minimo=100000 maximo=120000 < contabil.dat
11, 06-03-2000, INGRESO , 101300
34, 10-10-2000, INGRESO , 112000
Como es lógico 'awk' permite la declaración de funciones. Normalmente
se recurre a implementar una función cuando necesitamos una funcionalidad
que el lenguaje no proporciona de forma predefinida o cuando queremos
estructurar el código de un programa grande en fragmentos más pequeños
y por tanto más manejables.
function nombre_de_la_función ( lista_de_parámetros ) {
sentencias
}
function aleatorio ( minimo, maximo ){
return ( ( ( maximo - minimo + 1 ) * rand () ) + minimo ) ;
}
END{
for (i=0; i<20; i++){
printf ("%3d) Entre 1 y 6 = %3d Entre 5 y 15 =%3d\n",
i, aleatorio (1, 6), aleatorio(5, 15));
}
}
$ echo | awk -f dado.awk
0) Entre 1 y 6 = 5 Entre 5 y 15 = 12
1) Entre 1 y 6 = 6 Entre 5 y 15 = 7
2) Entre 1 y 6 = 6 Entre 5 y 15 = 7
3) Entre 1 y 6 = 5 Entre 5 y 15 = 13
4) Entre 1 y 6 = 3 Entre 5 y 15 = 8
5) Entre 1 y 6 = 3 Entre 5 y 15 = 6
6) Entre 1 y 6 = 4 Entre 5 y 15 = 7
7) Entre 1 y 6 = 6 Entre 5 y 15 = 7
8) Entre 1 y 6 = 5 Entre 5 y 15 = 6
9) Entre 1 y 6 = 3 Entre 5 y 15 = 10
10) Entre 1 y 6 = 6 Entre 5 y 15 = 14
11) Entre 1 y 6 = 5 Entre 5 y 15 = 10
12) Entre 1 y 6 = 6 Entre 5 y 15 = 11
13) Entre 1 y 6 = 4 Entre 5 y 15 = 12
14) Entre 1 y 6 = 5 Entre 5 y 15 = 15
15) Entre 1 y 6 = 2 Entre 5 y 15 = 9
16) Entre 1 y 6 = 1 Entre 5 y 15 = 9
17) Entre 1 y 6 = 3 Entre 5 y 15 = 14
18) Entre 1 y 6 = 2 Entre 5 y 15 = 14
19) Entre 1 y 6 = 2 Entre 5 y 15 = 7
Esta es una función fácil de usar que nos permite ejecutar un comando
del sistema operativo. En caso de éxito retorna 0, y en caso de error
retornará un valor distinto de cero.
$ awk ' BEGIN { if (system("ls") !=0) printf ("Error de ejecución\n"); }'
if (system("test -r " nombre_fich)) {
fprintf ("%s no encontrado\n", nombre_fich);
}
Este es es un apartado en el que más que explicar cosas nos vamos a limitar
a mencionar ciertas posibilidades. No podemos dedicar demasiado espacio
a este tipo de cuestiones avanzadas pero si con lo que en este apartado
contemos conseguimos ponerle los dientes largos nos daremos por satisfechos
aunque no entienda una palabra.
BEGIN {
"whoami" | getline usuario
if ( usuario ~ /root/ ) {
printf ("Soy superusuario\n");
}
else{
printf ("Soy usuario normal\n");
}
}
$ awk -f tipo_usuario.awk
Los array permiten el almacenamiento de una
serie de elementos que pueden ser accedidos mediante un índice.
En realidad los arrays de awk son más potentes que los arrays que
vimos cuando estudiamos la programación de la bourne-shell donde los
índices de un array eran siempre números enteros. Vamos a usar
en primer lugar los arrays de esta forma. Es decir nos vamos a limitar
en los primeros ejemplos a usar números enteros como índices.
$ ps axl > ps-axl.out ; cat ps-axl.out
FLAGS UID PID PPID PRI NI SIZE RSS WCHAN STA TTY TIME COMMAND
100 0 1 0 0 0 756 0 do_select SW ? 0:03 (init)
40 0 2 1 0 0 0 0 bdflush SW ? 0:18 (kflushd)
40 0 3 1 0 0 0 0 kupdate SW ? 0:18 (kupdate)
840 0 4 1 0 0 0 0 kpiod SW ? 0:00 (kpiod)
840 0 5 1 0 0 0 0 kswapd SW ? 0:15 (kswapd)
140 0 186 1 0 0 900 200 do_select S ? 0:00 /sbin/sys
140 0 188 1 0 0 1016 0 do_syslog SW ? 0:00 (klogd)
140 1 193 1 0 0 780 0 do_select SW ? 0:00 (portmap)
140 0 195 1 0 0 860 0 do_select SW ? 0:00 (inetd)
140 0 200 1 0 0 764 108 nanosleep S ? 0:00 /usr/sbin
140 0 210 1 0 0 908 0 do_select SW ? 0:00 (lpd)
40 31 226 1 0 0 3784 0 do_select SW ? 0:00 (postmast
140 0 237 1 5 0 1728 316 do_select S ? 0:00 sendmail:
140 0 241 1 0 0 1292 184 do_select S ? 0:01 /usr/sbin
40 0 244 1 0 0 1544 56 do_select S ? 0:00 /usr/bin/
40 1 254 1 0 0 840 96 nanosleep S ? 0:00 /usr/sbin
40 0 257 1 5 0 860 164 nanosleep S ? 0:00 /usr/sbin
140 0 262 1 0 0 1780 60 do_select S ? 0:07 /usr/sbin
100 0 268 1 0 0 1964 616 read_chan S 1 0:00 -bash
100 0 269 1 0 0 836 0 read_chan SW 2 0:00 (getty)
100 1001 270 1 0 0 2096 724 wait4 S 3 0:00 -bash
100 0 271 1 0 0 836 0 read_chan SW 4 0:00 (getty)
100 0 272 1 0 0 836 0 read_chan SW 5 0:00 (getty)
100 1001 273 1 0 0 2088 1408 wait4 S 6 0:00 -bash
140 33 274 262 0 0 1792 0 wait_for_co SW ? 0:00 (apache)
140 33 275 262 0 0 1792 0 flock_lock_ SW ? 0:00 (apache)
140 33 276 262 0 0 1792 0 flock_lock_ SW ? 0:00 (apache)
140 33 277 262 0 0 1792 0 flock_lock_ SW ? 0:00 (apache)
140 33 278 262 0 0 1792 0 flock_lock_ SW ? 0:00 (apache)
0 1001 916 270 0 0 3536 1640 do_select S 3 0:00 vi awk1.d
0 1001 1029 273 0 0 1916 668 wait4 S 6 0:00 xinit /ho
100 0 1034 1029 12 0 8824 3280 do_select S ? 0:02 X :0 -bpp
0 1001 1037 1029 0 0 4620 2748 do_select S 6 0:01 mwm
40 1001 1042 1037 0 0 1728 924 wait4 S 6 0:00 bash /hom
40 1001 1045 1037 0 0 1728 924 wait4 S 6 0:00 bash /hom
0 0 1050 1042 0 0 2976 1872 do_select S 6 0:00 xterm -ls
0 1001 1058 1045 0 0 2320 1220 do_select S 6 0:00 xclock -d
100 1001 1051 1050 14 0 2080 1400 wait4 S p0 0:00 -bash
100000 1001 1074 1051 17 0 1068 528 R p0 0:00 ps axl
$ pstree -p > pstree-p.out ; cat pstree-p.out
init(1)-+-apache(262)-+-apache(274)
| |-apache(275)
| |-apache(276)
| |-apache(277)
| `-apache(278)
|-atd(254)
|-bash(268)
|-bash(270)---vi(916)
|-bash(273)---xinit(1029)-+-XF86_S3V(1034)
| `-mwm(1037)-+-.xinitrc(1042)---xterm(1050)---bash(1051)---pstree(1068)
| `-.xinitrc(1045)---xclock(1058)
|-cron(257)
|-getty(269)
|-getty(271)
|-getty(272)
|-gpm(200)
|-inetd(195)
|-kflushd(2)
|-klogd(188)
|-kpiod(4)
|-kswapd(5)
|-kupdate(3)
|-lpd(210)
|-portmap(193)
|-postmaster(226)
|-sendmail(237)
|-sshd(241)
|-syslogd(186)
`-xfs(244)
BEGIN { ind=0; }
function padre(p){
for (i=0; i
$ awk -f ancestros.awk proc=1051 < ps-axl.out
1051->1050->1042->1037->1029->273->1->
FTLSUSE |CURSOS |FTLinuxCourse para SuSE | 11800
FTLREDH |CURSOS |FTLinuxCourse para RedHat | 11800
ASUSCOM |HARDWARE|Asuscom ISDNLink 128k Adapter (PCI) | 6865
RAILROAD|JUEGOCOM|Railroad Tycoon (Gold Edition) | 7700
CIVILIZ |JUEGOCOM|Civilization: Call to power | 7700
MYTHII |JUEGOCOM|Myth II | 7700
LIAPPDEV|LIBROS |Linux Application Development (537 Páginas) | 11000
CONECT01|LIBROS |Guía del Usuario de Linux (413 Páginas) | 5300
CONECT03|LIBROS |Guía del Servidor (Conectiva Linux 437 Páginas) | 5300
CONECT02|LIBROS |Guía del Administrador de redes (465 Páginas) | 5300
LIUSRESU|LIBROS |Linux User's Resource (795 Páginas) | 12000
RH70DLUX|LINUXCOM|RedHat Linux 7.0 Deluxe en español | 9600
RH70PROF|LINUXCOM|RedHat Linux 7.0 Profesional en Español | 20000
SUSE70 |LINUXCOM|Suse Linux 7.0 (6CDs)(Version española) | 6850
RTIME22 |LINUXCOM|RealTime 2.2 (1CD) | 13000
CONCT50E|LINUXCOM|Conectiva Linux 5.0 Versión Económica Español (6CDs) | 5200
CITIUS22|LINUXCOM|Linux Citius 2.2 | 7750
TRBLIW60|LINUXCOM|Turbolinux Workstation 6.0 | 6500
MOTIF |LINUXCOM|Motif Complete | 22000
CONCTSRV|LINUXCOM|Conectiva Linux Ed.Servidor (Español 3CDs + 4 Manua | 27500
RHORA8I |LINUXCOM|RedHat Linux Enterprise Edition optimized for Oracle8i |270000
MANDRA72|LINUXCOM|Mandrake 7.2 (7CDs) PowerPack Deluxe (versión española| 8300
PINGUINO|SUSEPROM|Pingüino de peluche | 6000
BEGIN {
FS="[\ \t]*\|[\ \t]*" ;
while ( getline < "articulos.dat" > 0) {
artic[$1]= "(" $4 " Ptas + Iva) " $3;
printf ("%s ", $1);
}
for (;;){
printf ("\n\nIntroduzca un código de artículo o solo
$ awk -f articulos.awk
FTLSUSE FTLREDH ASUSCOM RAILROAD CIVILIZ MYTHII LIAPPDEV CONECT01 CONECT03
CONECT02 LIUSRESU RH70DLUX RH70PROF SUSE70 RTIME22 CONCT50E CITIUS22 TRBLIW60
MOTIF CONCTSRV RHORA8I MANDRA72 PINGUINO
Introduzca un código de artículo o solo
Si quiere hacernos llegar alguna duda, aclaración,
crítica, o contribución personal, utilice nuestro
formulario de contacto y nosotros le contestaremos