You are on page 1of 10

EL LENGUAJE AWK

1.- Introduccion.
awk es una de esos programas-joya con que nos obsequia el
mundo UNIX. Conocido y apreciado por los usuarios mas
avanzados y notablemente desconocido por el resto, es el
lenguaje que, entre otras cosas, podria ahorrar mucho tiempo,
dinero y esfuerzo de programacion para gestion de pequeñas y
medianas bases de datos, personales o de empresas. Puede
sustituir tambien con ventaja a programas shell que combinen
filtros como grep, sed, join, cut, paste y otros, ya que los
programas tipicos awk constan de una o dos lineas, y esto es
suficiente para efectuar operaciones notablemente
sofisticadas.
awk fue originalmente diseñado e implementado por A.H. Aho,
B.W. Kernighan y P.J. Weinberger ( de ahi awk ) en 1977, en
parte como un experimento para mostrar como las herramientas
UNIX grep y sed podian generalizarse para trabajar con numeros
ademas de texto. En particular, los autores estaban
interesados en expresiones regulares y editores programables.
Aunque diseñado para escribir pequeños programas, pronto
atrajo a gran numero de programadores, quienes produjeron
muchos programas extensos que pusieron de manifiesto las
limitaciones de la implementacion original. Por eso, en 1985
la version original fue mejorada.
En su estado actual, awk es un lenguaje tremendamente
versatil, que ha sido ý es usado para administracion de bases
de datos, diseño de circuitos, analisis numerico, graficos,
diseño e implementacion de compiladores, administracion del
sistema, lenguaje de entrada para no programadores y cursos de
ingenieria de software.
2.- Empezando con awk.
Antes de nada, una advertencia. Este tutor esta basado
estrechamente en el libro "The awk programming language", de
A.V. Aho, B.W. Kernighan y P.J. Weinberger. Si le es posible,
prescinda de este humilde resumen y acuda al original.
2.1.- Primer programa.
En adelante, usaremos una pequeña base de datos ficticia.
Suponemos que se encuentra en el archivo datos.emp y contiene
las siguientes lineas, donde el primer campo es un nombre, el
segundo un precio por hora y el segundo el numero de horas
trabajadas.
Belen 600 0
Daniel 500 0
Marcial 657 20
Maria 450 12
Susana 525 14
El programa que calcula el sueldo de todo aquel empleado que
ha trabajado mas de 0 horas es el siguiente:
awk ' $3 > 0 { print $1, $2*$3} ' datos.emp
Este programa invoca al interprete de awk con `awk', para que
ejecute el programa que se da entre comillas, y que tomara la
entrada del archivo `datos.emp'. Su salida es la siguiente:
Marcial 13140
Maria 5400
Susana 7350
Los programas awk constan esencialmente de parejas patron-
accion. En el ejemplo que nos ocupa, el patron lo satisfacen
aquellas lineas cuyo tercer campo es mayor que cero, y la
accion consiste en imprimir el primer campo, el nombre, y el
producto del segundo por el tercero, el sueldo. Como segundo
ejemplo, el programa awk que imprime el nombre de las personas
que no han trabajado es el siguiente:
awk ' $3 == 0 { print $1}' datos.emp
y su salida es la siguiente:
Belen
Daniel
Normalmente, es mas comodo usar la sintaxis
awk -f programa entrada
donde `programa' es el fichero con el programa awk que se va a
ejecutar y `entrada' es una lista de uno o varios archivos
sobre los que va actual el programa. Si se omite esta lista,
awk tomara la entrada estandard.
Si el programa awk contiene un error, el interprete avisara
del mismo, y no intentara ejecutarlo. Sin embargo, existen
errores (p. ej. division por cero) que solo pueden detectarse
en tiempo de ejecucion. En este caso, awk detendra el programa
y dara un mensaje de error.
2.2.- Imprimiendo campos y lineas
Hemos usando antes la instruccion `print $1', que indica que
se imprima el primer campo de la linea. Cuando se omite el
argumento de print, se imprime la linea entera. Por defecto,
toda linea impresa por awk termina en un caracter `0, y los
argumento de print separados por una coma se imprimen
separados por un espacio en blanco, aunque este comportamiento
puede cambiarse.
En cuanto a la forma de referirse a los campos, no es
obligatorio un digito tras el caracter `$', sino que puede
usarse cualquier expresion que produzca un valor entero.
awk tiene muchas variables internas. Por ejemplo, `NF' es el
numero de campos de cada linea, a medida que estas se leen.
Una instruccion legal podria ser entonces:
{print NF, $1, $NF}
Otra de las variables internas de awk es `NR', que lleva la
cuenta de las lineas leidas hasta el momento. Puede usarse
para numerar las lineas a la salida. Por ejemplo, el programa
{print NR,$0}
produce la salida
1 Belen 600 0
2 Daniel 500 0
3 Marcial 657 20
4 Maria 450 12
5 Susana 525 14
Ademas de variables, print puede colocar texto. Para ello, se
le proporciona como argumento, escribiendolo entre comillas.
Pruebese por ejemplo el programa:
{ print "la paga de ",$1," es de ",$2*$3," euros."}
Normalmente, sobre todo cuando se van a obtener listados
largos, deseamos un minimo formato para la salida, de manera
que los campos similares aparezcan encolumnados. En el ejemplo
que nos esta sirviendo de guia, la salida ganaria mucha
legibilidad si el sueldo de cada empleado se imprimiese en la
misma columna. Para cubrir estas necesidades se usa la funcion
`printf', cuya sintaxis es igual a la funcion homologa del
lenguaje C:
printf formato, valor1, valor2, valor3, ...
donde `formato' es una cadena que contiene texto para ser
impreso tal cual e intercaladas en este texto especificaciones
sobre como deben imprimirse los valores que van a
continuacion. Una especificacion de formato consta del
caracter `%' seguido de algunos caracteres que dependen del
tipo de formato en particular que se desea, por ejemplo, `s'
para cadenas, `f' para numeros reales. De esta forma, nuestro
primer programa podriamos reescribirlo como:
$3 > 0 { printf "%-8s .... %8.1f euros\n",$1,$2*$3) } datos.emp
obteniendo la salida
Marcial .... 13140.0 euros
Maria .... 5400.0 euros
Susana .... 7350.0 euros
Aqui, `%-8s' indica que se va a imprimir una cadena (s),
justificada a la izquierda (-) en un campo de 8 caracteres de
ancho. El `-8' es opcional. De la misma forma, `%8.1f' indica
que se imprima un numero real, en un campo de ocho caracteres
de ancho, con un decimal.
2.3.- Combinando patrones.
Los patrones puede combinarse mediante los operadores logicos
AND, OR y NOT, que en awk se escriben respectivamente `&&',
`||' y `!'. Por ejemplo, para imprimir los nombres de aquellos
empleados que ganan mas de 400 euros a la hora (ciertamente
bien pagados!) y que han trabajado mas de 15 horas:
$2>=400 && $3>=15 {print $1}
que da la salida
Marcial
Observese que aquellas lineas que satisfacen mas de una
condicion son impresas solo una vez.
2.4.- Validando datos.
--Siempre-- hay errores en los datos reales, y awk es una
excelente herramienta de depuracion. La validacion es un
proceso esencialmente negativo, es decir, se advierte de las
lineas que pueden contener datos erroneos, y no se dice nada
de las lineas correctas. El siguiente programa muestra como
puede usarse awk para validacion de datos. Este es un programa
de varias lineas, y por tanto no es practico introducirlo
desde linea de comandos. Mejor ponerlo en un fichero, p. ej.
`valida.awk', y ejecutarlo mediante `awk -f valida.awk
datos.emp'.
NF != 3 { print $0, "numero de campos distinto de 3" }
$2 < 3.35 { print $0, "precio por hora demasiado bajo" }
$2 > 10 { print $0, "precio por hora demasiado alto" }
$3 < 0 { print $0, "horas trabajadas negativas" }
$3 > 60 { print $0, "demasiadas horas trabajadas" }
2.5.- Bloques BEGIN y END.
Un bloque BEGIN contiene entre llaves una serie de acciones
que se llevan a cabo antes de que se procese la primera linea
del archivo. Por ejemplo, ejecute el siguiente programa awk:
BEGIN { print "NOMBRE EUR/HORA HORAS"
print " "
}
{ print NR, $0 }
Este programa no consta de patrones, unicamente de acciones.
El bloque BEGIN le pone nombre a las columnas e imprime una
linea en blanco. El segundo bloque de acciones simplemente
imprime la linea, numerandola.
De la misma forma, existe un bloque END que se ejecuta despues
de que la ultima linea del archivo de entrada haya sido
procesada. Puede usarse por ejemplo para mostrar los calculos
realizados.
2.6.- Variables en awk.
awk permite el uso de variables numericas y cadenas, definidas
por el usuario. No es necesario declararlas. El siguiente
programa muestra la forma en que puede calcularse la paga
media de los empleados y la suma de las pagas:
BEGIN {
print "comenzamos ..."
total=0
}
{ total=total+$2*$3 }
END {
media=total/NR
print "cantidad total: " total
print "paga media : " media
print "programa ejecutado ..."
}
En realidad, no es preciso inicializar las variables que vayan
a usarse, salvo que su valor inicial sea distinto de cero.
Nosotros sin embargo preferimos inicializarlas explicitamente,
en aras de la claridad.
Una de las mejores caracteristicas de awk es que las variables
pueden almacenar indistintamente datos numericos o cadenas de
caracteres, y que puede operarse con las cadenas como si
fueran numeros. El siguiente programa imprime el nombre del
empleado que mas gana por hora, y el precio por hora.
BEGIN {
maximo = 0
empleado= " "
}
$2 > maximo { maximo = $2; empleado=$1 }
END {
print empleado " gana " maximo
}
Aqui la variable `maximo' es numerica, mientras que `empleado'
es de cadena. Observese que el bloque BEGIN es innecesario,
pero insistimos en ponerlo para hacer mas claro el programa.
Es posible efectuar concatenaciones de cadenas simplemente
escribiendolas una tras otra, separadas por un espacio en
blanco, y asignandolas a otra cadena. Por ejemplo, para formar
una cadena que contenga los nombres de todos los empleados,
usese el siguiente programa:
{ nombres = nombres " " $1 }
END {
print nombres
}
Al igual que existen variables incorporadas, como NR y NF,
existen funciones incorporadas. Por ejemplo, `length' devuelve
la longitud de una cadena. Su sintaxis es `length(cadena)'.
Veremos algunas mas adelante.
3.- Control de flujo.
Dada una pareja patron-accion, las estructuras de control de
flujo que proporciona awk son exclusivas de la parte `accion'.
3.1.- if-else
La sintaxis de esta instruccion es:
if (expresion) {
instrucciones
}
else {
instrucciones
}
Por ejemplo, para encontrar la media del dinero ganado por
aquellos empleados que han trabajado mas de treinta horas:
BEGIN {
n=0
pagado=0
}
$3 > 30 { n=n+1; pagado=pagado+$2*$3}
END {
if (n>0) {
print "numero de empleados: ", n
print "se pago de media : ", pagado/n
}
else
print "ningun empleado trabajo mas de treinta horas."
}
3.2.- while
La sintaxis de while es:
while (expresion){
instrucciones
}
por ejemplo, para calcular el capital acumulado dada una
cierta cantidad inicial que renta unos interes anuales:
# capital=capital_inicial*(1+interes)^años
{
i=1
while (i<=$3){
printf "%.2f0 , $1*(1+$2)^i
i=i+1
}
}
Se ha introducido un comentario, señalado mediante `#'.
3.3.- for
La sintaxis de for es igual a la de C, y condensa en una sola
linea la inicializacion de la variable de control del bucle,
el test y el incremento de dicha variable. Por ejemplo, el
programa con el que ilustramos el bucle while puede
reescribirse como:
{
for(i=1;i<=$3;i=i+1){
printf "%.2f0,$1*(1+$2)^i
}
}
4.- Arrays
Uno de los aspectos mas agradables de awk es la forma de
trabajar con arrays. Al igual que para los numeros y las
cadenas, no es preciso declararlos, y el interprete se encarga
de todo lo relacionado con el almacenamiento. El siguiente
programa usa un array llamado `lineas' para guardar un archivo
completo y luego imprimirlo en orden inverso:
{
linea[NR]=$0 # almacena cada linea
}
END {
for(i=NR;i>0;i=i-1){
print linea[i]
}
}
5.- Mas sobre patrones y expresiones regulares.
Un patron en un programa awk puede tener tres formas basicas:
a) /expresion_regular/
b) expresion ~ /expresion_regular/
c) expresion !~ /expresion_regular/
Por ejemplo, para buscar las lineas que contienen la cadena
`grecia' usamos el patron:
/grecia/
Para buscar aquellas lineas cuyo tercer campo contiene el
patron `españa' usamos:
$3 ~ /españa/
y para buscar aquellas lineas cuyo tercer campo --es-- la
cadena `españa' escribimos:
$3 ~ / españa /
mientra que para buscar las lineas cuyo segundo campo no
contiene la subcadena `13a':
$2 !~ /13a/
Una `expresion regular' es una cadena que usa una notacion
especial para identificar a una clase de cadenas. Los
metacaracteres de las expresiones regulares son los
siguientes:
^$ . [ ] | ( ) * + ?
Una expresion regular basica puede ser una de las siguientes:
a) un no-metacaracter, que se identifica a si mismo
b) una secuencia de escape, que identifica un caracter
especial, como por ejemplo para el tabulador
c) un metacaracter `barrado' (ej. que identifica
literalemente a ese metecaracter
d) ^, que identifica el principio de la linea
e) $, que identifica el final de la linea
f) ., que identifica un caracter simple
g) un conjunto [abc] que identifica al caracter `a', `b' o `c'
h) el complemento de una clase, que identifica cualquier caracter
escepto los pertenecientes a esa clase, por ejemplo [^0-9] para
cualquier caracter distinto de un digito.
Ademas, una expresion regular basica puede extenderse para
formar otras expresiones regulares mas complejas. Las formas
en que puede hacerse esta extension son:
a) A|B que identifica a A o B
b) AB que identifica a A seguida inmediatamente por B
c) A* que identifica a cero o mas A's
d) A+ que identifica a una o mas A's
e) A? que identifica a una o ninguna A
Ejemplos:
^[ABC] A o B o C al principio de una linea
^[^ABC] caracter distinto de A, B y C al principio de la linea
[^ABC] cualquier caracter distinto de A, B y C
^[^a-z]$ cualquier cadena de un solo caracter distinto de una
letra minuscula.
6.- Funciones.
6.1.- Funciones aritmeticas box center tab(%); c s c | c l |
l. Funciones aritmeticas% = Funcion%Valor devuelto =
atan2(x,y)% arcotangente de y/x entre -pi y pi cos(x)% coseno
de x, con x en radianes exp(x)% exponencial de x int(x)% parte
entera de x log(x)% logaritmo en base e de x rand()% numero
aleatorio entre 0 y 1 sin(x)% seno de x sqrt(x)% raiz de x
srand(x)% x es la nueva semilla para rand()
6.2.- Algunas funciones de cadena de caracteres.
box center tab(%); c s c | c l | l. Algunas funciones de
cadena% = Funcion%Descripcion = gsub(r,s)% sustituye s por r
en $0 gsub(r,s,t)% sustituye s por r en t index(s,t)% devuelve
0 o posicion de t en s length(s)% devuelve longitud de s
match(s,r)% si s contiene a r devuelve posicion.
6.3.- Funciones definidas por el usuario.
La sintaxis para declarar una funcion es:
function nombre( parametros ){
instrucciones
}
El cuerpo de la funcion puede contener la linea
return ( expresion )
donde ( expresion ) es opcional. Por ejemplo, para calcular el
maximo de entre dos argumentos:
function max(m,n){
if (m>=n) return m
if (n>m) return n
}
las variables m y n usadas en `max' son locales a esta
funcion.
7.- Entrada y salida de datos.
Hemos presentado anteriormente las funciones print y printf.
Existe la posibilidad de redirigir la salida de estas
funciones, bien a un archivo, bien a un cauce. Para redirigir
a un archivo, se usan los operadores `>' y `>>'. El primero
crea un archivo nuevo, o lo reescribe si es que ya existe,
mientras que el segundo añade la salida al final de dicho
archivo. Por ejemplo, el siguiente programa separa el archivo
de entrada en dos archivos, uno con aquellos registros cuyo
segundo campo es mayor o igual que 500 y otro con aquellos
registros cuyo segundo campo es menor que esa cantidad:
$2 >= 500 { print > "mayor.que"}
$2 < 500 { print > "menor.que"}
La apertura y cierre de los archivos es automatica. Ademas el
nombre de un archivo puede ser un literal o una expresion. Por
ejemplo, el programa:
{print > $1 }
pone cada linea en un archivo cuyo nombre es el del primer
campo. La salida tambien puede pasarse a un filtro mediante un
cauce. Por ejemplo, puede ordenarse:
{ print $2 | "sort" }
Observese que el comando destino del cauce se encuentra
entrecomillado.
En cuanto a la entrada de datos, existen varias formas de
proporcionarselos a un programa awk. La sintaxis normal es
awk -f programa datos
pero tambien puede usarse la salida de un comando a traves de
un cauce. Por ejemplo, podemos escribir un programa para
procesar las lineas que contengan la cadena '00Acx3--' en una
hipotetica base de datos. Pero el comando egrep de UNIX lo
hace mucho mas rapido. Por tanto, podemos usar egrep para
seleccionar las lineas y pasar la salida al programa awk:
egrep '00Acx3--' <archivo> | awk <programa>
Frecuentemente, `linea' y `registro' son sinonimos, pero
ocurre a veces que un registro contiene muchos campos y no es
comodo tenerlos todos en la misma linea. awk tiene la
capacidad de tratar registros multilinea. Para ello, basta
con especificar que el caracter separador de campos es `\n' y
que el caracter separador de registros es la cadena vacia:
BEGIN { RS=""; FS="0 }
8.- Para terminar.
Este breve resumen cubre una parte del lenguaje awk suficiente
para la mayoria de las pequeñas aplicaciones que se necesiten.
Pero vuelvo a recomendar el libro "The awk programming
language", de -A-ho, -W-einberger y -K-ernighan, Addison-
Wesley, 1988. Este pequeño libro es una delicia por varios
motivos, pero uno de ellos es la enorme cantidad de
aplicaciones que se exponen.
Asi, el capitulo 3 trata de transformacion y reduccion de
datos, validacion de datos, fusion de varios archivos en uno
solo y registros multilinea. El capitulo 4 explica como
gestionar una base de datos relacional con awk. El capitulo 5
se dedica al procesamiento de palabras, formateo de texto y
gestion automatica de indices y referencias cruzadas en
documentos, principalmente.
El capitulo 6 hara las delicias de los amantes de los
lenguajes de programacion y de los amantes de los computadores
en general. Trata de la implementacion de pequeños lenguajes
de programacion. Por ejemplo, describe un pequeño lenguaje
ensamblador e implementa un ensamblador de dos pasadas y un
interprete para los programas escritos en ese lenguaje
ensamblador. El codigo fuente del ensamblador e interprete
ocupa cuarenta y cuatro (44) lineas de codigo, incluidos
comentarios. En este capitulo se describen tambien un lenguaje
para graficos, una calculadora postfija y un analizador
recursivo descendente.
El capitulo 7 se dedica a la experimentacion con algoritmos
clasicos de ordenamiento y busqueda, y da una version de la
herramienta `make' escrita de forma facil en awk.

You might also like