Renuncia de responsabilidad:
La versión original de este artículo fue publicada por IBM
developerWorks y es propiedad de Westtech Information Services. Este
documento es una versión actualizada del artículo original y contiene
mejoras introducidas por el Equipo de Documentación de Gentoo.
Este documento carece de soporte activo.
|
Awk mediante ejemplos, Parte 1
1.
Introducción al gran lenguaje de nombre extraño
En defensa de awk
En esta serie de artículos, te transformaré en un programador
productivo con awk. Admito que awk no tiene un nombre particularmente
bonito, y que la versión GNU de awk, llamada gawk, suena simplemente
rara. Los que no estén familiarizados con el lenguaje, al oír "awk"
podrían pensar en un código desordenado y anticuado que sería capaz de
volver loco al más avispado de los gurús de UNIX (haciéndole gritar
repetidamente "kill -s9!" mientras corre hacia la máquina de café).
Awk no tiene un gran nombre. Pero es un gran lenguaje. Awk está
enfocado al procesamiento de texto y la generación de reportes, pero
también posee muchas características que permiten la programación
seria. Y, al contrario que otros lenguajes, la sintaxis de awk es
bastante familiar y toma prestados algunos de los mejores elementos de
lenguajes como C, python y bash (aunque técnicamente, awk fue creado
antes que python y bash. Awk es uno de estos lenguajes que, una vez
aprendido se convierte en una parte imprescindible de nuestro arsenal
de codificación.
El primer awk
Listado de Código 1.1: El primer awk |
$ awk '{ print }' /etc/passwd
|
Deberías ver el contenido de tu archivo /etc/passwd
aparecer ante tus ojos. Ahora explicaré lo que awk ha hecho. Cuando lo
hemos llamado, hemos especificado /etc/passwd como
nuestro fichero de entrada. Al ejecutar awk, éste ha evaluado el
comando para cada línea de /etc/passwd, por orden. Toda
la salida se manda a stdout, y así obtenemos una salida idéntica a la
que nos daría el comando cat sobre el fichero
/etc/pass.
Y ahora, una explicación sobre el bloque de código { print }. En awk,
las llaves se usan para englobar bloques de código, de forma similar a
C. Dentro de nuestro bloque de código, tenemos un solo comando
print. En awk, cuando un comando print aparece solo, se imprime todo
el contenido de la línea.
Listado de Código 1.2: Imprimiendo la línea actual |
$ awk '{ print $0 }' /etc/passwd
$ awk '{ print "" }' /etc/passwd
|
En awk, la variable $0 representa la línea actual, por tanto, print y
print $0 hacen exactamente lo mismo.
Listado de Código 1.3: Llenar la pantalla con un texto |
$ awk '{ print "hiya" }' /etc/passwd
|
Múltiples campos
Listado de Código 1.4: print $1 |
$ awk -F":" '{ print $1 $3 }' /etc/passwd
halt7
operator11
root0
shutdown6
sync5
bin1
|
Listado de Código 1.5: print $1 $3 |
$ awk -F":" '{ print $1 " " $3 }' /etc/passwd
|
Listado de Código 1.6: $1$3 |
$ awk -F":" '{ print "username: " $1 "\t\tuid:" $3 }' /etc/passwd
username: halt uid:7
username: operator uid:11
username: root uid:0
username: shutdown uid:6
username: sync uid:5
username: bin uid:1
|
Guiones externos
Listado de Código 1.7: Guión de ejemplo |
BEGIN { FS=":" }
{ print $1 }
|
La diferencia entre estos dos métodos consiste en cómo establecemos el
separador de campo. En este guión, el separador es especificado en el
mismo código (estableciendo la variable FS). Mientras que en nuestro
anterior ejemplo, FS se establece a través de la opción -F":". Por
norma general es mejor establecerlo dentro del mismo guión,
simplemente porque será un argumento menos que tendrás que tendrás que
recordar teclear. Cubriremos la variable FS en más detalle más tarde
en este mismo artículo.
The BEGIN and END blocks
Normalmente, awk ejecuta cada bloque de código del guión una vez por
cada línea de entrada que se le proporcione. Sin embargo, hay muchas
situaciones donde se necesita ejecutar un código de inicialización
antes de que awk comience a procesar el texto del fichero de
entrada. Para dichas situaciones, awk permite definir un bloque
BEGIN. Hemos usado un bloque BEGIN en el ejemplo anterior. Como el
bloque BEGIN es evaluado antes de que awk procese el fichero de
entrada, es un lugar excelente para inicializar la variable FS
(separador de campo), imprimir una cabecera o inicializar otras
variables globales a las que podrás hacer referencia más adelante en
el programa.
Awk también provee otro bloque especial, llamado el bloque END. Awk
ejecuta este bloque tras procesar todas las líneas del fichero de
entrada. Típicamente, el bloque END se usa para realizar cálculos
finales o imprimir sumarios que deban aparecer al final del flujo de
salida.
Expresiones regulares y bloques
Listado de Código 1.8: Regular expressions and blocks |
/foo/ { print }
/[0-9]+\.[0-9]*/ { print }
|
Expresiones y bloques
Listado de Código 1.9: fredprint |
$1 == "fred" { print $3 }
|
Listado de Código 1.10: root |
$5 ~ /root/ { print $3 }
|
Sentencias condicionales
Listado de Código 1.11: if |
{
if ( $5 ~ /root/ ) {
print $3
}
}
|
Ambos guiones funcionan de forma idéntica. En el primer ejemplo, la
expresión booleana se pone fuera del bloque, mientras que en el
segundo, el bloque se ejecuta para cada línea, y la impresión se
realiza de forma selectiva usando una sentencia if. Ambos métodos son
válidos, y puedes escoger el que mejor se adapte a tu guión.
Listado de Código 1.12: if if |
{
if ( $1 == "foo" ) {
if ( $2 == "foo" ) {
print "uno"
} else {
print "one"
}
} else if ($1 == "bar" ) {
print "two"
} else {
print "three"
}
}
|
Listado de Código 1.13: if |
! /matchme/ { print $1 $3 $4 }
|
Listado de Código 1.14: if |
{
if ( $0 !~ /matchme/ ) {
print $1 $3 $4
}
}
|
Ambos guiones imprimirán solo aquellas líneas que no contengan una
cadena de caracteres matchme. De nuevo, puedes escoger el que mejor se
ajuste a tu código. Ambos hacen lo mismo.
Listado de Código 1.15: Imprimiendo los campos iguales a foo y bar |
( $1 == "foo" ) && ( $2 == "bar" ) { print }
|
Este ejemplo imprimirá solo aquellas líneas en las que el campo 1 sea
igual a foo y el campo 2 sea igual a bar.
¡Variables numéricas!
En el bloque BEGIN, inicializamos nuestra variable entera x a
cero. Luego, cada vaz que awk encuentra una línea en blanco, ejecutará
la sentencia x=x+1, incrementando x. Tras ser procesadas todas las
líneas, el bloque END se ejecutará, y awk imprimirá un sumario final
especificando el número total de líneas en blanco que ha encontrado.
Variables no interpretables como numéricas
Listado de Código 1.16: Campo ejemplo |
2.01
|
Listado de Código 1.17: 1.01x$( )1.01 |
{ print ($1^2)+1 }
|
Si experimentas un poco, encontrarás que si una variable no contiene
un número válido, awk tratará dicha variable numéricamente como un
cero si es evaluada en alguna expresión matemática.
Montones de operadores
Otra cosa buena de awk es su gran cantidad complementaria de
operadores matemáticos. Además de la suma, la resta, la multiplicación
y la división, awk nos permite usar el operador de exponenciación "^",
el operador módulo (restos) "%" y y un surtido de operadores de
asignación que ha tomado prestados de C.
Estos incluyen pre- y post-incremento/decremento ( i++, --foo ) y
operadores de asignación combinados con suma, resta, multiplicación o
división ( a+=3, b*=2, c/=2.2, d-=6.2 ). Pero eso no es todo --
también podemos usar útiles operadores de asignación combinados con
operaciones de modulo o exponenciación ( a^=2, b%=4 ).
Separadores de campo
Awk tienes su propio surtido de variables especiales. Algunas nos
permiten afinar el comportamiento de awk, mientras otras pueden ser
leídas para obtener información valiosa sobre la entrada. Ya hemos
usado una de estas variables especiales: FS. Como se mención antes,
ésta nos permite especificar la secuencia de caracteres que awk espera
encontrar como separador de campo. Cuando usamos
/etc/passwd como entrada, FS se fijó a ":". Si bien esto
funcionó en ese momento, FS nos permite mucha más flexibilidad.
Listado de Código 1.18: Otro separador de campo |
FS="\t+"
|
Arriba usando el operador especial de expresiones regulares "+", que
significa "una ocurrencia o más del carácter anterior".
Listado de Código 1.19: Fijando FS como espacio en blanco |
FS="[[:space:]+]"
|
Si bien esta asignación funcionará, no es necesaria. ¿Por qué? Porque
por defecto, FS está prefijado como un solo espacio en blanco, que awk
interpreta como "uno o más espacios o tabuladores". En este ejemplo en
particular, ¡el valor predeterminado de FS era exactamente lo que
andabas buscando desde primera hora!
Listado de Código 1.20: Ejemplo de separador de campo |
FS="foo[0-9][0-9][0-9]"
|
Número de campos
Listado de Código 1.21: Número de campos |
{
if ( NF > 2 ) {
print $1 " " $2 ":" $3
}
}
|
Número de registro
Listado de Código 1.22: Número de registro |
{
if ( NR > 10 ) {
print "ok, ¡ahora a por la información!"
}
}
|
Awk provee variables adicionales que pueden ser usadas para varios
propósitos. Veremos más de estas variables en artículos posteriores.
Hemos llegado al final de nuestra exploración inicial de awk. Conforme
la serie avance, demostraré funcionalidades más avanzadas de awk, y
terminaremos la serie con una aplicación real en awk. Mientras tanto,
si quieres aprender más, examina la lista de recursos listada más
abajo.
2.
Recursos
Enlaces útiles
-
Lee otros artículos de Daniel sobre awk publicados en
developerWorks: Awk mediante ejemplos, Parte 2 y Parte
3.
-
Si prefieres un buen libro a la vieja usanza, el sed & awk, 2ª
Edición de O'Reilly es una buena opción.
-
Asegúrate también de examinar comp.lang.awk
FAQ. También contiene varios enlaces sobre awk.
-
El tutorial
de awk de Patrick Hartigan viene con varios guiones
interesantes de awk.
-
El Compilador TAWK de
Thompson compila guiones de awk transformándolos en binarios
ejecutables. Hay versiones para Windows, OS/2, DOS, y UNIX.
-
La
guía de usuario de GNU Awk también está disponible para
referencia en línea.
|