AWK ( / ɔː k / [4] ) es un lenguaje específico de dominio diseñado para el procesamiento de texto y que se utiliza normalmente como herramienta de extracción y generación de informes de datos . Al igual que sed y grep , es un filtro , [4] y es una característica estándar de la mayoría de los sistemas operativos tipo Unix .
El lenguaje AWK es un lenguaje de programación basado en datos que consiste en un conjunto de acciones que se deben realizar contra flujos de datos textuales (ya sea que se ejecuten directamente en archivos o se utilicen como parte de una canalización ) con el fin de extraer o transformar texto, como producir informes formateados. El lenguaje utiliza ampliamente el tipo de datos string , matrices asociativas (es decir, matrices indexadas por cadenas clave) y expresiones regulares . Si bien AWK tiene un dominio de aplicación previsto limitado y fue especialmente diseñado para admitir programas de una sola línea , el lenguaje es Turing-completo , e incluso los primeros usuarios de AWK de Bell Labs a menudo escribían programas AWK grandes y bien estructurados. [5]
AWK fue creado en Bell Labs en la década de 1970, [6] y su nombre se deriva de los apellidos de sus autores: Alfred Aho (autor de egrep ), Peter Weinberger (que trabajó en pequeñas bases de datos relacionales) y Brian Kernighan . El acrónimo se pronuncia igual que el nombre de la especie de ave auk , que se ilustra en la portada de The AWK Programming Language . [7] Cuando se escribe en minúsculas, como awk
, se refiere al programa Unix o Plan 9 que ejecuta scripts escritos en el lenguaje de programación AWK.
Según Brian Kernighan, uno de los objetivos de AWK era tener una herramienta que pudiera manipular fácilmente tanto números como cadenas. AWK también se inspiró en el lenguaje de programación de Marc Rochkind que se usaba para buscar patrones en los datos de entrada y se implementó utilizando yacc . [8]
Como una de las primeras herramientas que aparecieron en la versión 7 de Unix , AWK agregó características computacionales a una tubería de Unix además del shell Bourne , el único lenguaje de scripting disponible en un entorno Unix estándar. Es una de las utilidades obligatorias de la Single UNIX Specification [ 9] y es requerida por la especificación Linux Standard Base [10] .
En 1983, AWK fue una de varias herramientas UNIX disponibles para el sistema operativo UNOS de Charles River Data Systems bajo la licencia de Bell Laboratories . [11]
AWK fue revisado y ampliado significativamente entre 1985 y 1988, lo que dio como resultado la implementación de GNU AWK escrita por Paul Rubin, Jay Fenlason y Richard Stallman , publicada en 1988. [12] GNU AWK puede ser la versión más ampliamente implementada [13] porque está incluida en los paquetes Linux basados en GNU. GNU AWK ha sido mantenido únicamente por Arnold Robbins desde 1994. [12] El código fuente nawk (New AWK) de Brian Kernighan se publicó por primera vez en 1993 sin publicidad, y públicamente desde fines de la década de 1990; muchos sistemas BSD lo usan para evitar la licencia GPL. [12]
AWK fue precedido por sed (1974). Ambos fueron diseñados para el procesamiento de texto. Comparten el paradigma orientado a líneas e impulsado por datos, y son particularmente adecuados para escribir programas de una sola línea , debido al bucle principal implícito y las variables de línea actual. La potencia y la brevedad de los primeros programas AWK (en particular, el potente manejo de expresiones regulares y la concisión debido a las variables implícitas, que facilitan los programas de una sola línea), junto con las limitaciones de AWK en ese momento, fueron inspiraciones importantes para el lenguaje Perl (1987). En la década de 1990, Perl se volvió muy popular, compitiendo con AWK en el nicho de los lenguajes de procesamiento de texto Unix.
AWK lee la entrada línea por línea. Se escanea una línea en busca de cada patrón del programa y, por cada patrón que coincida, se ejecuta la acción asociada.
— Alfred V. Aho [14]
Un programa AWK es una serie de pares de acciones de patrones, escritos como:
condición { acción } condición { acción } ...
donde la condición es típicamente una expresión y la acción es una serie de comandos. La entrada se divide en registros, donde por defecto los registros están separados por caracteres de nueva línea para que la entrada se divida en líneas. El programa prueba cada registro contra cada una de las condiciones por turno, y ejecuta la acción para cada expresión que sea verdadera. Se puede omitir tanto la condición como la acción. La condición por defecto coincide con cada registro. La acción por defecto es imprimir el registro. Esta es la misma estructura de patrón-acción que sed.
Además de una expresión AWK simple, como foo == 1
o /^foo/
, la condición puede ser BEGIN
o END
que hace que la acción se ejecute antes o después de que se hayan leído todos los registros, o patrón1, patrón2 que coincide con el rango de registros comenzando con un registro que coincide con el patrón1 hasta e incluyendo el registro que coincide con el patrón2 antes de intentar nuevamente coincidir con el patrón1 en líneas subsiguientes.
Además de los operadores aritméticos y lógicos normales, las expresiones AWK incluyen el operador tilde, ~
que hace coincidir una expresión regular con una cadena. Como sintaxis útil , /regexp/ sin utilizar el operador tilde hace coincidir con el registro actual; esta sintaxis deriva de sed , que a su vez la heredó del editor ed , donde /
se utiliza para realizar búsquedas. Esta sintaxis de utilizar barras como delimitadores para expresiones regulares fue adoptada posteriormente por Perl y ECMAScript , y ahora es común. El operador tilde también fue adoptado por Perl.
Los comandos AWK son las instrucciones que se sustituyen por acciones en los ejemplos anteriores. Los comandos AWK pueden incluir llamadas a funciones, asignaciones de variables, cálculos o cualquier combinación de estos. AWK contiene soporte integrado para muchas funciones; muchas más son proporcionadas por las distintas versiones de AWK. Además, algunas versiones admiten la inclusión de bibliotecas vinculadas dinámicamente , que también pueden proporcionar más funciones.
El comando de impresión se utiliza para generar texto. El texto de salida siempre termina con una cadena predefinida denominada separador de registros de salida (ORS), cuyo valor predeterminado es una nueva línea. La forma más simple de este comando es:
print
print $1
print $1, $3
Aunque estos campos ( $X ) pueden parecerse a variables (el símbolo $ indica variables en los shells habituales de Unix y en Perl ), en realidad se refieren a los campos del registro actual. Un caso especial, $0 , se refiere a todo el registro. De hecho, los comandos " print
" y " print $0
" son idénticos en funcionalidad.
El comando de impresión también puede mostrar los resultados de cálculos y/o llamadas de funciones:
/regex_pattern/ { # Acciones a realizar en caso de que el registro (línea) coincida con el patrón regex_pattern anterior print 3 + 2 print foobar ( 3 ) print foobar ( variable ) print sin ( 3 - 2 ) }
La salida se puede enviar a un archivo:
/regex_pattern/ { # Acciones a realizar en caso de que el registro (línea) coincida con el patrón regex_pattern anterior print "expression" > "file name" }
o a través de una tubería :
/regex_pattern/ { # Acciones a realizar en caso de que el registro (línea) coincida con el patrón regex_pattern anterior print "expression" | "command" }
Las variables integradas de Awk incluyen las variables de campo: $1, $2, $3, etc. ($0 representa el registro completo). Contienen el texto o los valores de los campos de texto individuales de un registro.
Otras variables incluyen:
NR
: Número de registros. Mantiene un recuento actual de la cantidad de registros de entrada leídos hasta el momento de todos los archivos de datos. Comienza en cero, pero nunca se restablece automáticamente a cero. [15]FNR
: Número de registros del archivo. Mantiene un recuento actual de la cantidad de registros de entrada leídos hasta el momento en el archivo actual. Esta variable se restablece automáticamente a cero cada vez que se inicia un nuevo archivo. [15]NF
: Número de campos. Contiene el número de campos en el registro de entrada actual. El último campo del registro de entrada se puede designar con $NF, el penúltimo campo con $(NF-1), el antepenúltimo campo con $(NF-2), etc.FILENAME
:Contiene el nombre del archivo de entrada actual.FS
: Separador de campos. Contiene el "separador de campos" utilizado para dividir los campos en el registro de entrada. El valor predeterminado, "espacio en blanco", permite cualquier secuencia de espacios y caracteres de tabulación. FS se puede reasignar con otro carácter o secuencia de caracteres para cambiar el separador de campos.RS
: Separador de registros. Almacena el carácter "separador de registros" actual. Dado que, de manera predeterminada, una línea de entrada es el registro de entrada, el carácter separador de registros predeterminado es una "nueva línea".OFS
: Separador de campo de salida. Almacena el "separador de campo de salida", que separa los campos cuando Awk los imprime. El valor predeterminado es un carácter de "espacio".ORS
: Separador de registros de salida. Almacena el "separador de registros de salida", que separa los registros de salida cuando Awk los imprime. El valor predeterminado es un carácter de "nueva línea".OFMT
: Formato de salida. Almacena el formato de salida numérica. El formato predeterminado es "%.6g".Los nombres de las variables pueden utilizar cualquiera de los caracteres [A-Za-z0-9_], con la excepción de las palabras clave del lenguaje. Los operadores + - * / representan suma, resta, multiplicación y división, respectivamente. Para la concatenación de cadenas , simplemente coloque dos variables (o constantes de cadena) una al lado de la otra. Es opcional utilizar un espacio entre ellas si se trata de constantes de cadena, pero dos nombres de variable colocados uno junto al otro requieren un espacio entre ellos. Las comillas dobles delimitan las constantes de cadena. Las instrucciones no necesitan terminar con punto y coma. Finalmente, se pueden agregar comentarios a los programas utilizando # como el primer carácter de una línea o detrás de un comando o secuencia de comandos.
En un formato similar al de C , las definiciones de funciones constan de la palabra clave function
, el nombre de la función, los nombres de los argumentos y el cuerpo de la función. A continuación, se muestra un ejemplo de una función.
función add_three ( número ) { devolver número + 3 }
Esta declaración puede invocarse de la siguiente manera:
( patrón ) { print add_three ( 36 ) # Salidas '''39''' }
Las funciones pueden tener variables que se encuentran en el ámbito local. Los nombres de estas se agregan al final de la lista de argumentos, aunque los valores de estas se deben omitir al llamar a la función. Es una convención agregar algunos espacios en blanco en la lista de argumentos antes de las variables locales, para indicar dónde terminan los parámetros y comienzan las variables locales.
Aquí está el programa habitual "¡Hola, mundo!" escrito en AWK:
COMIENZA { imprimir "¡Hola, mundo!" salir }
Imprimir todas las líneas de más de 80 caracteres. La acción predeterminada es imprimir la línea actual.
longitud ( $ 0 ) > 80
Cuente palabras en la entrada e imprima el número de líneas, palabras y caracteres (como wc ):
{ palabras += caracteres NF += longitud + 1 # agregue uno para tener en cuenta el carácter de nueva línea al final de cada registro (línea) } FIN { imprimir NR , palabras , caracteres }
Como no hay un patrón para la primera línea del programa, cada línea de entrada coincide de manera predeterminada, por lo que las acciones de incremento se ejecutan para cada línea. words += NF
es una abreviatura de words = words + NF
.
{ s += $ NF } FIN { imprimir s + 0 }
s se incrementa con el valor numérico de $NF , que es la última palabra en la línea según lo definido por el separador de campos de AWK (por defecto, espacio en blanco). NF es el número de campos en la línea actual, p. ej. 4. Dado que $4 es el valor del cuarto campo, $NF es el valor del último campo en la línea independientemente de cuántos campos tenga esta línea, o si tiene más o menos campos que las líneas circundantes. $ es en realidad un operador unario con la precedencia de operador más alta . (Si la línea no tiene campos, entonces NF es 0, $0 es la línea completa, que en este caso está vacía aparte de un posible espacio en blanco, y por lo tanto tiene el valor numérico 0).
Al final de la entrada, el patrón END coincide, por lo que se imprime s . Sin embargo, dado que es posible que no haya habido líneas de entrada en absoluto, en cuyo caso nunca se ha asignado ningún valor a s , por defecto será una cadena vacía. Añadir cero a una variable es un modismo de AWK para convertirla de una cadena a un valor numérico. (Concatenar una cadena vacía es convertirla de un número a una cadena, p. ej. s "" . Tenga en cuenta que no hay ningún operador para concatenar cadenas, solo se colocan adyacentes). Con la coerción, el programa imprime "0" en una entrada vacía, sin ella, se imprime una línea vacía.
NR % 4 == 1 , NR % 4 == 3 { printf "%6d %s\n" , NR , $ 0 }
La instrucción de acción imprime cada línea numerada. La función printf emula la función printf estándar de C y funciona de manera similar al comando print descrito anteriormente. Sin embargo, el patrón para hacer coincidir funciona de la siguiente manera: NR es el número de registros, normalmente líneas de entrada, que AWK ha leído hasta el momento, es decir, el número de línea actual, comenzando en 1 para la primera línea de entrada. % es el operador de módulo . NR % 4 == 1 es verdadero para la 1.ª, 5.ª, 9.ª, etc., líneas de entrada. Del mismo modo, NR % 4 == 3 es verdadero para la 3.ª, 7.ª, 11.ª, etc., líneas de entrada. El patrón de rango es falso hasta que la primera parte coincide, en la línea 1, y luego permanece verdadero hasta que coincide la segunda parte, en la línea 3 inclusive. Luego permanece falso hasta que la primera parte coincide nuevamente en la línea 5.
De esta forma, el programa imprime las líneas 1, 2 y 3, se salta la línea 4, y luego la 5, 6 y 7, y así sucesivamente. Para cada línea, imprime el número de línea (en un campo de 6 caracteres de ancho) y luego el contenido de la línea. Por ejemplo, cuando se ejecuta en esta entrada:
RomaFlorenciaMilánNápolesTurínVenecia
El programa anterior imprime:
1 Roma 2 Florencia 3 Milán 5 Turín 6 Venecia
Como caso especial, cuando la primera parte de un patrón de rango es constantemente verdadera, p. ej. 1 , el rango comenzará al principio de la entrada. De manera similar, si la segunda parte es constantemente falsa, p. ej. 0 , el rango continuará hasta el final de la entrada. Por ejemplo,
/^--cortar aquí--$/ , 0
imprime líneas de entrada desde la primera línea que coincide con la expresión regular ^--cut here--$ , es decir, una línea que contiene solo la frase "--cut here--", hasta el final.
Frecuencia de palabras utilizando matrices asociativas :
BEGIN { FS = "[^a-zA-Z]+" } { para ( i = 1 ; i <= NF ; i ++ ) palabras [ tolower ( $ i )] ++ } END { para ( i en palabras ) imprimir i , palabras [ i ] }
El bloque BEGIN establece el separador de campo en cualquier secuencia de caracteres no alfabéticos. Los separadores pueden ser expresiones regulares. Después de eso, llegamos a una acción simple, que realiza la acción en cada línea de entrada. En este caso, para cada campo de la línea, sumamos uno al número de veces que aparece esa palabra, primero convertida a minúsculas. Finalmente, en el bloque END, imprimimos las palabras con sus frecuencias. La línea
para (yo en palabras)
crea un bucle que recorre la matriz words y asigna i a cada subíndice de la matriz. Esto es diferente de la mayoría de los lenguajes, donde un bucle de este tipo recorre cada valor de la matriz. El bucle imprime así cada palabra seguida de su recuento de frecuencia. tolower
fue una adición al One True awk (ver más abajo) realizada después de la publicación del libro.
Este programa se puede representar de varias maneras. La primera utiliza el shell Bourne para crear un script de shell que lo hace todo. Es el más corto de estos métodos:
#!/bin/shpatrón = " $1 " shift
awk '/' " $patrón " '/ { print NOMBRE_ARCHIVO ": " $0 }' " $@ "
El $pattern
comando awk no está protegido por comillas simples, por lo que el shell expande la variable, pero debe colocarse entre comillas dobles para manejar correctamente los patrones que contienen espacios. Un patrón por sí solo, de la manera habitual, verifica si la línea completa ( $0
) coincide. FILENAME
contiene el nombre de archivo actual. awk no tiene un operador de concatenación explícito; dos cadenas adyacentes las concatenan. $0
se expande a la línea de entrada original sin cambios.
Existen otras formas de escribir esto. Este script de shell accede al entorno directamente desde awk:
#!/bin/shexportar patrón = " $1 " shift
awk '$0 ~ ENVIRON["patrón"] { imprimir NOMBRE_ARCHIVO ": " $0 }' " $@ "
Este es un script de shell que utiliza ENVIRON
, una matriz introducida en una versión más reciente de One True awk después de que se publicó el libro. El subíndice de ENVIRON
es el nombre de una variable de entorno; su resultado es el valor de la variable. Esto es como la función getenv en varias bibliotecas estándar y POSIX . El script de shell crea una variable de entorno pattern
que contiene el primer argumento, luego descarta ese argumento y hace que awk busque el patrón en cada archivo.
~
Comprueba si su operando izquierdo coincide con su operando derecho; !~
es su inverso. Una expresión regular es simplemente una cadena y se puede almacenar en variables.
El siguiente método utiliza la asignación de variables mediante la línea de comandos, en la que un argumento de awk puede verse como una asignación a una variable:
#!/bin/shpatrón = " $1 " shift
awk '$0 ~ patrón { print NOMBRE_ARCHIVO ": $0 }' "patrón= $patrón " " $@ "
O puede utilizar la opción de línea de comando -v var=value (por ejemplo awk -v pattern="$pattern" ... ).
Finalmente, esto está escrito en awk puro, sin ayuda de un shell o sin la necesidad de saber demasiado sobre la implementación del script awk (como lo hace la asignación de variables en la línea de comandos), pero es un poco largo:
BEGIN { patrón = ARGV [ 1 ] para ( i = 1 ; i < ARGC ; i ++ ) # eliminar el primer argumento ARGV [ i ] = ARGV [ i + 1 ] ARGC -- si ( ARGC == 1 ) { # el patrón era lo único, por lo que se fuerza la lectura desde la entrada estándar (usada por el libro) ARGC = 2 ARGV [ 1 ] = "-" } } $ 0 ~ patrón { imprimir NOMBRE_ARCHIVO ": $ 0 }
Es BEGIN
necesario no solo extraer el primer argumento, sino también evitar que se interprete como un nombre de archivo una vez que BEGIN
finaliza el bloque. ARGC
, el número de argumentos, siempre se garantiza que sea ≥1, al igual que ARGV[0]
el nombre del comando que ejecutó el script, con mayor frecuencia la cadena "awk"
. ARGV[ARGC]
es la cadena vacía, ""
. #
inicia un comentario que se expande hasta el final de la línea.
Tenga en cuenta que el if
bloque awk solo verifica si debe leer desde la entrada estándar antes de ejecutar el comando. Esto significa que
awk 'prog'
¡Solo funciona porque el hecho de que no haya nombres de archivos se comprueba antes de prog
ejecutarse! Si lo configura explícitamente ARGC
en 1 para que no haya argumentos, awk simplemente se cerrará porque considera que no hay más archivos de entrada. Por lo tanto, debe indicar explícitamente que se lea desde la entrada estándar con el nombre de archivo especial -
.
En sistemas operativos tipo Unix se pueden construir scripts AWK autónomos utilizando la sintaxis shebang .
Por ejemplo, un script que envía el contenido de un archivo determinado a la salida estándar se puede crear creando un archivo print.awk
con el siguiente contenido:
#!/usr/bin/awk -f { imprimir $ 0 }
Se puede invocar con:./print.awk <filename>
Le -f
dice a awk que el argumento que sigue es el archivo desde el que se leerá el programa AWK, que es el mismo indicador que se utiliza en sed. Dado que se utilizan a menudo para líneas de comandos, ambos programas ejecutan de forma predeterminada un programa dado como argumento de línea de comandos, en lugar de un archivo independiente.
AWK se escribió originalmente en 1977 y se distribuyó con la versión 7 de Unix .
En 1985 sus autores comenzaron a expandir el lenguaje, principalmente agregando funciones definidas por el usuario. El lenguaje se describe en el libro The AWK Programming Language , publicado en 1988, y su implementación se puso a disposición en versiones de UNIX System V. Para evitar confusiones con la versión anterior incompatible, a esta versión a veces se la llamó "new awk" o nawk . Esta implementación se lanzó bajo una licencia de software libre en 1996 y aún es mantenida por Brian Kernighan (ver enlaces externos a continuación). [ cita requerida ]
Las versiones antiguas de Unix, como UNIX/32V , incluían awkcc
, que convertía AWK a C. Kernighan escribió un programa para convertir awk a C++; se desconoce su estado. [16]
tolower
y ENVIRON
que se explican anteriormente; consulte el archivo FIXES en el archivo fuente para obtener más detalles. Esta versión es utilizada por, por ejemplo, Android , FreeBSD , NetBSD , OpenBSD , macOS e illumos . Brian Kernighan y Arnold Robbins son los principales contribuyentes a un repositorio fuente para nawk : github.com/onetrueawk/awk .El manual de gawk tiene una lista de más implementaciones de Awk. [24]
[AWK] se denomina a menudo un lenguaje basado en datos: las instrucciones del programa describen los datos de entrada que se deben comparar y procesar en lugar de una secuencia de pasos del programa
El lenguaje de acción awk es Turing-completo y puede leer y escribir archivos.