#!/usr/bin/awk -f
# Los comentarios tienen este aspecto.
# Los programas AWK son una colección de patrones y acciones. El patrón más
# importante es BEGIN. Las acciones van en bloques delimitados por llaves.
BEGIN {
# BEGIN correrá al inicio del programa. Es donde pones todo el código
# preliminar antes de procesar los archivos de texto. Si no tienes archivos
# de texto, piensa en BEGIN como el punto de entrada principal del script.
# Las variables son globales. Asígnalas o úsalas sin declararlas.
count = 0
# Los operadores son justo como en C (y amigos).
a = count + 1
b = count - 1
c = count * 1
d = count / 1
e = count % 1 # módulo
f = count ^ 1 # exponenciación
a += 1
b -= 1
c *= 1
d /= 1
e %= 1
f ^= 1
# Incremento y decremento en uno
a++
b--
# Como un operador prefijo, regresa el valor modificado
++a
--b
# Nota que no hay puntación para terminar las instrucciones
# Instrucciones de control
if (count == 0)
print "Iniciando count en 0"
else
print "Eh?"
# O puedes usar el operador ternario
print (count == 0) ? "Iniciando count en 0" : "Eh?"
# Bloques formados por múltiples líneas usan llaves
while (a < 10) {
print "La concatenación de strings se hace " " con series "
print " de" " strings separados por espacios"
print a
a++
}
for (i = 0; i < 10; i++)
print "El viejo confiable ciclo for"
# Los operaciones de comparación son estándar...
a < b # Menor que
a <= b # Menor o igual que
a != b # No igual
a == b # Igual
a > b # Mayor que
a >= b # Mayor o igual que
# ...así como los operadores lógicos
a && b # AND
a || b # OR
# Además están las expresiones regulares
if ("foo" ~ "^fo+$")
print "Fooey!"
if ("boo" !~ "^fo+$")
print "Boo!"
# Arrays
arr[0] = "foo"
arr[1] = "bar"
# Desafortunadamente no hay otra manera de inicializar un array.
# Tienes que inicializar cada posición del array.
# También hay arrays asociativos
assoc["foo"] = "bar"
assoc["bar"] = "baz"
# Y arrays multidimensionales con limitaciones que no mencionaré aquí
multidim[0,0] = "foo"
multidim[0,1] = "bar"
multidim[1,0] = "baz"
multidim[1,1] = "boo"
# Puedes probar pertenencia a un array
if ("foo" in assoc)
print "Fooey!"
# También puedes usar el operador 'in' para iterar las claves de un array
for (key in assoc)
print assoc[key]
# La terminal es un array especial llamado ARGV
for (argnum in ARGV)
print ARGV[argnum]
# Puedes eliminar elementos de un array.
# Esto es útil para prevenir que AWK suponga que algunos argumentos
# son archivos por procesar.
delete ARGV[1]
# El número de argumentos de la terminal está en la variable ARGC
print ARGC
# AWK tiene tres categorías de funciones incluidas.
# Demostraré esas funciones posteriormente.
return_value = arithmetic_functions(a, b, c)
string_functions()
io_functions()
}
# Así se define una función
function arithmetic_functions(a, b, c, localvar) {
# Probablemente la parte más molesta de AWK es que no hay variables locales
# Todo es global. No es problema en scripts pequeños, pero sí para
# scripts más grandes.
# Hay un work-around (mmm... hack). Los argumentos de las funciones son
# locales para la función, y AWK permite definir más argumentos de función
# de los que necesita, por lo que define las variables locales en la
# declaración como en la función de arriba. Como convención, agrega
# espacios en blanco para distinguir los parámetros de la función de las
# variables locales. En este ejemplo, a, b y c son parámetros y localvar es una
# variable local.
# Ahora, a demostrar las funciones aritméticas
# La mayoría de las implementaciones de AWK tienen funciones
# trigonométricas estándar
localvar = sin(a)
localvar = cos(a)
localvar = atan2(b, a) # arcotangente de b / a
# Y cosas logarítmicas
localvar = exp(a)
localvar = log(a)
# Raíz cuadrada
localvar = sqrt(a)
# Trucar un flotante a entero
localvar = int(5.34) # localvar => 5
# Números aleatorios
srand() # La semilla es el argumento. Por defecto usa el tiempo del sistema
localvar = rand() # Número aleatorio entre 0 y 1.
# Y aquí se regresa el valor
return localvar
}
function string_functions( localvar, arr) {
# AWK tiene algunas funciones para procesamiento de strings,
# y muchas dependen fuertemente en expresiones regulares.
# Buscar y remplazar, primer instancia (sub) o todas las instancias (gsub)
# Ambas regresan el número de matches remplazados.
localvar = "fooooobar"
sub("fo+", "Meet me at the ", localvar) # localvar => "Meet me at the bar"
gsub("e", ".", localvar) # localvar => "m..t m. at th. bar"
# Buscar una cadena que haga match con una expresión regular
# index() hace lo mismo, pero no permite expresiones regulares
match(localvar, "t") # => 4, dado que 't' es el cuarto caracter
# Separar con base en un delimitador
split("foo-bar-baz", arr, "-") # a => ["foo", "bar", "baz"]
# Otras funciones útiles
sprintf("%s %d %d %d", "Testing", 1, 2, 3) # => "Testing 1 2 3"
substr("foobar", 2, 3) # => "oob"
substr("foobar", 4) # => "bar"
length("foo") # => 3
tolower("FOO") # => "foo"
toupper("foo") # => "FOO"
}
function io_functions( localvar) {
# Ya has visto print
print "Hello world"
# También hay printf
printf("%s %d %d %d\n", "Testing", 1, 2, 3)
# AWK no tiene handles de archivos en sí mismo. Automáticamente abrirá un
# handle de archivo cuando use algo que necesite uno. El string que usaste
# para esto puede ser tratada como un handle de archivo para propósitos de I/O.
# Esto lo hace similar al scripting de shell:
print "foobar" >"/tmp/foobar.txt"
# Ahora el string "/tmp/foobar.txt" es un handle. Puedes cerrarlo:
close("/tmp/foobar.txt")
# Aquí está como correr algo en el shell
system("echo foobar") # => muestra foobar
# Lee una línea de la entrada estándar (stdin) y lo guarda en localvar
getline localvar
# Lee una línea desde un pipe
"echo foobar" | getline localvar # localvar => "foobar"
close("echo foobar")
# Lee una línea desde un archivo y la guarda en localvar
getline localvar <"/tmp/foobar.txt"
close("/tmp/foobar.txt")
}
# Como dije al inicio, los programas en AWK son una colección de patrones y
# acciones. Ya conociste el patrón BEGIN. otros patrones sólo se usan si estás
# procesando líneas desde archivos o stdin.
# Cuando pasas argumentos a AWK, son tratados como nombres de archivos a
# procesar. Los va a procesar todos, en orden. Imagínalos como un ciclo for
# implícito, iterando sobre las líneas de estos archivos. Estos patrones y
# acciones son como instrucciones switch dentro del ciclo.
/^fo+bar$/ {
# Esta acción se ejecutará por cada línea que haga match con la expresión
# regular /^fo+bar$/, y será saltada por cualquier línea que no haga match.
# Vamos a sólo mostrar la línea:
print
# ¡Wow, sin argumento! Eso es porque print tiene uno por defecto: $0.
# $0 es el nombre de la línea actual que se está procesando.
# Se crea automáticamente para ti.
# Probablemente puedas adivinar que hay otras variables $. Cada línea es
# separada implícitamente antes de que se llame cada acción, justo como lo
# hace shell. Y, como shell, cada campo puede ser accesado con $.
# Esto mostrará el segundo y cuarto campos de la línea
print $2, $4
# AWK automáticamente define muchas otras variables que te ayudan a
# inspeccionar y procesar cada línea. La más importante es NF
# Imprime el número de campos de esta línea
print NF
# Imprime el último campo de esta línea
print $NF
}
# Cada patrón es realmente un prueba de verdadero/falso. La expresión regular
# en el último patrón también es una prueba verdadero/falso, pero parte de eso
# estaba oculto. Si no le das un string a la prueba, supondrá $0, la línea que
# se está procesando. La versión completa de esto es:
$0 ~ /^fo+bar$/ {
print "Equivalente al último patrón"
}
a > 0 {
# Esto se ejecutará una vez por línea, mientras a sea positivo
}
# Y ya te das una idea. Procesar archivos de texto, leyendo una línea a la vez,
# y haciendo algo con ella, particularmente separando en un deliminator, es tan
# común en UNIX que AWK es un lenguaje de scripting que hace todo eso por ti
# sin que tengas que pedirlo. Basta con escribir los patrones y acciones
# basados en lo que esperas de la entrada y lo quieras quieras hacer con ella.
# Aquí está un ejemplo de un script simple, para lo que AWK es perfecto.
# El script lee un nombre de stdin y muestra el promedio de edad para todos los
# que tengan ese nombre. Digamos que como argumento pasamos el nombre de un
# archivo con este contenido:
#
# Bob Jones 32
# Jane Doe 22
# Steve Stevens 83
# Bob Smith 29
# Bob Barker 72
#
# Éste es el script:
BEGIN {
# Primero, pedir al usuario el nombre
print "¿Para qué nombre quieres el promedio de edad?"
# Recuperar una línea de stdin, no de archivos en la línea de comandos
getline name <"/dev/stdin"
}
# Ahora, hacer match con cada línea cuyo primer campo es el nombre dado
$1 == name {
# Aquí dentro tenemos acceso a variables útiles precargadas:
# $0 es toda la línea
# $3 es el tercer campo, la edad, que es lo que nos interesa
# NF es el número de campos, que debe ser 3
# NR es el número de registros (líneas) vistos hasta ahora
# FILENAME es el nombre del archivo que está siendo procesado
# FS es el campo separador, " " en este caso
# Y muchas más que puedes conocer ejecutando 'man awk' en la terminal.
# Llevar el registro de la suma y cuantas líneas han hecho match.
sum += $3
nlines++
}
# Otro patrón especial es END. Va a ejecutarse después de procesar todos los
# archivos de texto. A diferencia de BEGIN, sólo se ejecuta si le das dado una
# entrada a procesar. Se ejecutará después de que todos los archivos hayan sido
# leídos y procesados según las reglas y acciones que programaste. El propósito
# es usualmente para mostrar un reporte final, o hacer algo con el agregado de
# los datos que has acumulado durante la ejecución del script.
END {
if (nlines)
print "La edad promedio para " name " es " sum / nlines
}