ant

AWK
4.14 II

sig

4.14.9 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'

BEGIN {
FS="\|" ;
cont_reg_luz=0;
cont_importe_luz=0; }

/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) ;}
Vamos a ejecutar este ejemplo y vamos a ver su salida
$ 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
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'.
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);
}
Vamos a ejecutar este ejemplo y vamos a ver su salida
$ awk -f contabil3.awk < contabil.dat

3, 01-01-2004, -       ,      96,         96  
4, 16-12-2004, AGUA    ,  -14650,     -14554
5, 05-01-2004, LUZ     ,  -15797,     -30351
6, 24-01-2004, GAS     ,  -34175,     -64526
7, 27-01-2004, INGRESO ,  141200,      76674
8, 01-02-2004, MENS    ,  -96092,     -19418  
9, 25-02-2004, LUZ     ,  -12475,     -31893 
10, 01-03-2004, MENS    ,  -96092,    -127985 
11, 06-03-2004, INGRESO ,  101300,     -26685 
12, 01-04-2004, MENS    ,  -96092,    -122777 
13, 06-04-2004, AGUA    ,  -15859,    -138636 
14, 07-04-2004, INGRESO ,  134000,      -4636 
15, 01-05-2004, MENS    ,  -98975,    -103611 
16, 02-05-2004, LUZ     ,  -11449,    -115060 
17, 09-05-2004, INGRESO ,   95000,     -20060 
18, 23-05-2004, GAS     ,  -21428,     -41488 
19, 25-05-2004, GAS     ,  -16452,     -57940 
20, 01-06-2004, MENS    ,  -98975,    -156915 
21, 07-06-2004, INGRESO ,  130000,     -26915 
22, 01-07-2004, MENS    ,  -98975,    -125890 
23, 04-07-2004, LUZ     ,  -12403,    -138293 
24, 07-07-2004, AGUA    ,   -5561,    -143854 
25, 10-07-2004, INGRESO ,   99000,     -44854 
26, 24-07-2004, GAS     ,  -11948,     -56802
27, 01-08-2004, MENS    ,  -98975,    -155777 
28, 10-08-2004, INGRESO ,  122355,     -33422 
29, 04-09-2004, LUZ     ,  -12168,     -45590
30, 10-09-2004, INGRESO ,  129000,      83410 
31, 19-09-2004, AGUA    ,  -10529,      72881 
32, 28-09-2004, GAS     ,   -2620,      70261 
33, 01-10-2004, MENS    ,  -98975,     -28714
34, 10-10-2004, INGRESO ,  112000,      83286

4.14.10 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 'if'

if ( expresión_lógica )
accion1
[ else
accion2 ]


Sentencia condicional con los operadores '?' y ':'

expresion_lógica ? accion1 : accion2


Bucle 'while'

while ( expresión_lógica )
accion

Bucle 'do' 'while'

do
accion
while ( expresión_lógica )

Bucle 'for'

for ( inicializar_contador ; comprobar_contador ; modificar_contador )
accion


Dentro de los bucles podemos usar break para forzar la salida de un bucle o continue para saltar a la siguiente iteración.

Veremos de momento tan solo un ejemplo para la sentencia condicional 'if'.
Edite el siguiente fichero que llamaremos 'contabil4.awk'
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); }
}
Vamos a ejecutar este ejemplo y vamos a ver su salida
$ 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

4.14.11 Pasar valores al script awk

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.

Edite el siguiente fichero que llamaremos 'contabil5.awk'
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); }
}
Vamos a ejecutar este ejemplo pasando y vamos a ver su salida

$ awk -f contabil5.awk minimo=100000 maximo=120000 < contabil.dat
11, 06-03-2000, INGRESO , 101300
34, 10-10-2000, INGRESO , 112000
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'
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); }
}
Vamos a ejecutar este ejemplo pasando valores y vamos a ver su salida

$ awk -f contabil6.awk minimo=100000 maximo=120000 deliminador='|' < contabil.dat

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

$ awk -v delimitador='|' -f contabil6.awk minimo=100000 maximo=120000 < contabil.dat
11, 06-03-2000, INGRESO , 101300
34, 10-10-2000, INGRESO , 112000

4.14.12 Declaración de funciones

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.

La sintaxis es muy sencilla.
function nombre_de_la_función ( lista_de_parámetros ) {
sentencias
}


Por ejemplo para declarar una función que retorne un número aleatorio entre 1 y 6.
Edite un fichero con nombre 'dado.awk'.
function aleatorio ( minimo, maximo ){
return ( ( ( maximo - minimo + 1 ) * rand () ) + minimo ) ;
}

END {
for (i=0; i<10; i++){
printf ("%3d) Entre 1 y 6 = %3d Entre 5 y 15 =%3d\n",i, aleatorio (1, 6), aleatorio(5, 15));
}
}
Ahora lo ejecutamos $ 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

4.14.13 Función system

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"); }'

Por ejemplo si quisiéramos verificar la existencia de un fichero almacenado en la variable 'nombre_fich' tendríamos que hacer
if (system("test -r " nombre_fich)) {
fprintf ("%s no encontrado\n", nombre_fich);
}

4.14.14 La función getline y otras funciones avanzadas

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.

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'.
BEGIN {"whoami" | getline usuario
if ( usuario ~ /root/ ) {
printf ("Soy superusuario\n");
} else{ printf ("Soy usuario normal\n"); }
}
Ejecute lo con

$ awk -f tipo_usuario.awk

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.

4.14.15 Arrays

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.

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.

$ ps axl > salida-ps-axl ; cat salida-ps-axl

 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    
 140     0   244     1   0   0   1544    56 do_select   S   ?   0:00 /usr/bin/    
 140     1   254     1   0   0    840    96 nanosleep   S   ?   0:00 /usr/sbin    
 140     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   179     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 
 100  1001  1074  1051  17   0   1068   528             R   p0  0:00 ps axl 

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.

$ pstree -p > salida-pstree-p ; cat salida-pstree-p
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)


Solo vamos a procesar los campos PID y PPID. $3 y $4 respectivamente. Los meteremos en un par de arrays llamados pid y ppid.

BEGIN { ind=0;  }

function padre(p){  
 for (i=0; i", proc);   
    proc= padre(proc); 
}

while ( proc >= 1 )  printf ("\n\n");}
Ahora ejecutamos pasando el pid del proceso del cual deseamos averiguar su descendencia.

$ awk -f ancestros.awk proc=1051 < salida-ps-axl

1051->1050->1042->1037->1029->273->1->
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.

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: ");
			  getline codigo ;  
	      if (codigo == "" )  
			        break;    
			  printf ("\n<%s>\n%s", codigo, artic[codigo]);}
 }
$ 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 : RH70PROF
(20000 Ptas + Iva) RedHat Linux 7.0 Profesional en Español

Introduzca un código de artículo : CITIUS22
(7750 Ptas + Iva) Linux Citius 2.2

Introduzca un código de artículo :
$
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.

Si dispone de algún fichero de datos de interes personal saque una copia e intente realizar alguna utilidad en 'awk'.