AWK ( / ɔː k / [4] ) es un lenguaje de dominio específico diseñado para el procesamiento de texto y normalmente utilizado como herramienta de extracción de datos e informes. 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 secuencias de comandos basado en datos que consta de un conjunto de acciones que se deben realizar contra flujos de datos textuales (ya sea ejecutados directamente en archivos o utilizados como parte de una canalización ) con el fin de extraer o transformar texto, como producir texto formateado. informes. El lenguaje utiliza ampliamente el tipo de datos de cadena , 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 diseñado especialmente 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 grandes programas AWK 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 letras 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ó usando yacc . [8]
Como una de las primeras herramientas que apareció en la versión 7 de Unix , AWK agregó funciones computacionales a una canalización 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 Especificación Única UNIX , [9] y es requerida por la especificación Base Estándar de Linux . [10]
En 1983, AWK era 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 GNU AWK escrita por Paul Rubin, Jay Fenlason y Richard Stallman , lanzada en 1988. [12] GNU AWK puede ser la versión más implementada [13] porque se incluye con los paquetes de Linux basados en GNU. GNU AWK ha sido mantenido únicamente por Arnold Robbins desde 1994. [12] La fuente nawk (Nuevo AWK) de Brian Kernighan se publicó por primera vez en 1993 sin publicidad y públicamente desde finales de los años 1990; Muchos sistemas BSD lo utilizan para evitar la licencia GPL. [12]
AWK fue precedido por sed (1974). Ambos fueron diseñados para el procesamiento de textos. Comparten el paradigma orientado a líneas y basado en 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 actuales. El poder y la concisión de los primeros programas AWK (en particular el poderoso manejo de expresiones regulares y la concisión debido a variables implícitas, que facilitan las frases ingeniosas) 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 hizo muy popular, compitiendo con AWK en el nicho de los lenguajes de procesamiento de textos Unix.
AWK lee la entrada línea por línea. Se escanea una línea para cada patrón en el programa y, para cada patrón que coincide, se ejecuta la acción asociada.
—Alfred V. Aho [14]
Un programa AWK es una serie de pares de acciones de patrón, escritos como:
condición { acción } condición { acción } ...
donde la condición suele ser una expresión y la acción es una serie de comandos. La entrada se divide en registros, donde de forma predeterminada 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 con cada una de las condiciones por turno y ejecuta la acción para cada expresión que sea verdadera. Se puede omitir la condición o la acción. La condición por defecto coincide con todos los registros. La acción predeterminada 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
hacer 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 que comienzan con un registro que coincide con patrón1 hasta incluyendo el registro que coincide con el patrón2 antes de intentar nuevamente compararlo con el patrón1 en las líneas siguientes.
Además de los operadores lógicos y aritméticos normales, las expresiones AWK incluyen el operador de tilde, ~
que compara una expresión regular con una cadena. Como azúcar sintáctico útil , /regexp/ sin usar el operador de tilde coincide 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 uso de barras como delimitadores de expresiones regulares fue adoptada posteriormente por Perl y ECMAScript , y ahora es común. Perl también adoptó el operador de tilde.
Los comandos AWK son declaraciones que sustituyen a la acción en los ejemplos anteriores. Los comandos AWK pueden incluir llamadas a funciones, asignaciones de variables, cálculos o cualquier combinación de los mismos. AWK contiene soporte integrado para muchas funciones; Los distintos sabores de AWK proporcionan muchos más. 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 llamada 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 tener parecido con 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 al registro completo. 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 a funciones:
/regex_pattern/ { # Acciones a realizar en caso de que el registro (línea) coincida con el 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 regex_pattern anterior print "expresión" > "nombre de archivo" }
o a través de una tubería :
/regex_pattern/ { # Acciones a realizar en caso de que el registro (línea) coincida con el regex_pattern anterior print "expresión" | "dominio" }
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 en los campos de texto individuales de un registro.
Otras variables incluyen:
NR
: Número de registros. Mantiene un recuento actual del número 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 Archivo de Registros. Mantiene un recuento actual del número 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
: Numero 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 penúltimo campo con $(NF-2), etc.FILENAME
: Contiene el nombre del archivo de entrada actual.FS
: Separador de campo. Contiene el "separador de campos" utilizado para dividir campos en el registro de entrada. El valor predeterminado, "espacio en blanco", permite cualquier secuencia de caracteres de espacio y 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 forma 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 campos 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 para la salida numérica. El formato predeterminado es "%.6g".Los nombres de las variables pueden utilizar cualquiera de los caracteres [A-Za-z0-9_], a excepción de las palabras clave del idioma. 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 intermedio si se trata de constantes de cadena, pero dos nombres de variables colocados uno al lado del otro requieren un espacio intermedio. Las comillas dobles delimitan las constantes de cadena. Las declaraciones no necesitan terminar con punto y coma. Finalmente, se pueden agregar comentarios a los programas usando # como primer carácter en una línea, o detrás de un comando o secuencia de comandos.
En un formato similar a 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_tres ( número ) { devolver número + 3 }
Esta declaración se puede invocar de la siguiente manera:
( patrón ) { imprimir add_tres ( 36 ) # Salidas '''39''' }
Las funciones pueden tener variables que se encuentran en el ámbito local. Los nombres de estos se agregan al final de la lista de argumentos, aunque sus valores deben omitirse 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 habitual "¡Hola, mundo!" programa escrito en AWK:
BEGIN { print "¡Hola mundo!" salida }
Imprima todas las líneas de más de 80 caracteres. La acción predeterminada es imprimir la línea actual.
longitud ( $ 0 ) > 80
Cuente las 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 existe un patrón para la primera línea del programa, cada línea de entrada coincide de forma 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 en el valor numérico de $NF , que es la última palabra de la línea según lo definido por el separador de campo de AWK (por defecto, espacio en blanco). NF es el número de campos en la línea actual, por ejemplo, 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 posibles espacios en blanco, por lo que tiene el valor numérico 0).
Al final de la entrada, el patrón FINAL coincide, por lo que se imprime s . Sin embargo, dado que es posible que no haya ninguna línea de entrada, en cuyo caso nunca se ha asignado ningún valor a s , por defecto será una cadena vacía. Agregar 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 forzar de un número a una cadena, por ejemplo, s "" . Tenga en cuenta que no hay ningún operador para concatenar cadenas, simplemente se colocan adyacentes). Con la coerción, el programa imprime "0" en una entrada vacía , sin él, se imprime una línea vacía.
NR % 4 == 1 , NR % 4 == 3 { printf "%6d %s\n" , NR , $ 0 }
La declaración de acción imprime cada línea numerada. La función printf emula el printf estándar de C y funciona de manera similar al comando de impresión descrito anteriormente. El patrón a coincidir, sin embargo, funciona de la siguiente manera: NR es el número de registros, generalmente líneas de entrada, que AWK ha leído hasta ahora, 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 cierto para las líneas de entrada 1.ª, 5.ª, 9.ª, etc. Del mismo modo, NR % 4 == 3 es cierto para las líneas de entrada 3.ª, 7.ª, 11.ª, etc. El patrón de rango es falso hasta que la primera parte coincide, en la línea 1, y luego permanece verdadero hasta el momento en que coincide la segunda parte, en la línea 3. Luego permanece falso hasta que la primera parte vuelve a coincidir en la línea 5.
Por lo tanto, el programa imprime las líneas 1,2,3, omite la línea 4, luego la 5,6,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, por ejemplo 1 , el rango comenzará al principio de la entrada. De manera similar, si la segunda parte es constantemente falsa, por ejemplo 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 usando matrices asociativas :
BEGIN { FS = "[^a-zA-Z]+" } { para ( i = 1 ; i <= NF ; i ++ ) palabras [ tolower ( $ i )] ++ } FIN { 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, por 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 FIN, imprimimos las palabras con sus frecuencias. La línea
para (yo en palabras)
crea un bucle que recorre la matriz palabras , estableciendo i en cada subíndice de la matriz. Esto es diferente de la mayoría de los lenguajes, donde dicho bucle recorre cada valor de la matriz. Así, el bucle imprime 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 formas. El primero usa el shell Bourne para crear un script de shell que hace todo. Es el más corto de estos métodos:
#!/bin/shpatrón = " $1 " shift
awk '/' " $patrón " '/ { print FILENAME ":" $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 adecuadamente los patrones que contienen espacios. Un patrón por sí solo, de la forma habitual, comprueba si toda la línea ( $0
) coincide. FILENAME
contiene el nombre del archivo actual. awk no tiene ningún operador de concatenación explícito; dos cadenas adyacentes las concatenan. $0
se expande a la línea de entrada original sin cambios.
Hay formas alternativas de escribir esto. Este script de shell accede al entorno directamente desde awk:
#!/bin/shpatrón de exportación = " $1 " shift
awk '$0 ~ ENTORNO["patrón"] { print FILENAME ":" $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 la publicación del 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 awk busca el patrón en cada archivo.
~
comprueba si su operando izquierdo coincide con su operando derecho; !~
es su inversa. Una expresión regular es solo una cadena y se puede almacenar en variables.
La siguiente forma utiliza la asignación de variables en 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 FILENAME ":" $0 }' "patrón= $patrón " " $@ "
O puede utilizar la opción de línea de comando -v var=value (por ejemplo, awk -v patrón="$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 comando), pero es un poco largo:
BEGIN { patrón = ARGV [ 1 ] for ( i = 1 ; i < ARGC ; i ++ ) # eliminar el primer argumento ARGV [ i ] = ARGV [ i + 1 ] ARGC -- if ( ARGC == 1 ) { # el el patrón era lo único, así que fuerce la lectura desde la entrada estándar (usada por el libro) ARGC = 2 ARGV [ 1 ] = "-" } } $ 0 ~ patrón { print FILENAME ":" $ 0 }
Es BEGIN
necesario no sólo para extraer el primer argumento, sino también para evitar que se interprete como un nombre de archivo una vez BEGIN
finalizado el bloque. ARGC
, siempre se garantiza que el número de argumentos sea ≥1, al igual ARGV[0]
que el nombre del comando que ejecutó el script, generalmente 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 el if
bloque. awk solo verifica si debe leer la entrada estándar antes de ejecutar el comando. Esto significa que
awk 'progreso'
¡Solo funciona porque el hecho de que no haya nombres de archivos solo se verifica antes de prog
ejecutarlo! Si lo establece 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 decir 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 nombre:
#!/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 cual leer el programa AWK, que es el mismo indicador que se usa en sed. Dado que a menudo se utilizan para frases ingeniosas, ambos programas ejecutan de forma predeterminada un programa proporcionado como argumento de línea de comandos, en lugar de un archivo separado.
AWK fue escrito originalmente en 1977 y distribuido con la Versión 7 de Unix .
En 1985, sus autores comenzaron a ampliar el lenguaje, sobre todo añadiendo funciones definidas por el usuario. El lenguaje se describe en el libro The AWK Programming Language , publicado en 1988, y su implementación estuvo disponible en las versiones de UNIX System V. Para evitar confusiones con la versión anterior incompatible, esta versión a veces se denominaba "nueva awk" o nawk . Esta implementación se lanzó bajo una licencia de software libre en 1996 y Brian Kernighan aún la mantiene (consulte los enlaces externos a continuación). [ cita necesaria ]
Se incluían versiones antiguas de Unix, como UNIX/32Vawkcc
, que convertían AWK a C. Kernighan escribió un programa para convertir awk a C++; se desconoce su estado. [dieciséis]
tolower
y ENVIRON
que se explican anteriormente; consulte el archivo FIXES en el archivo fuente para obtener más detalles. Esta versión la utilizan, por ejemplo, Android , FreeBSD , NetBSD , OpenBSD , macOS e illusmos . Brian Kernighan y Arnold Robbins son los principales contribuyentes a un repositorio de código fuente para nawk : github.com/onetrueawk/awk .[AWK] a menudo se denomina lenguaje basado en datos: las declaraciones del programa describen los datos de entrada que deben coincidir y procesarse en lugar de una secuencia de pasos del programa.
El lenguaje de acción awk está completo en Turing y puede leer y escribir archivos.