- GNU
awk recorre automáticamente stdin y archivos, y resuelve tareas de texto en la CLI combinando filtrado, sustitución y manejo de campos en comandos de una sola línea
/regexp/ y !/regexp/ son sintaxis abreviadas para inspeccionar la línea de entrada actual, y si la condición es verdadera, por defecto se imprime $0
sub reemplaza solo la primera coincidencia, gsub reemplaza todas, y si se omite el objetivo, la sustitución se aplica a la línea de entrada actual $0
- Gracias a la separación de campos por espacios y a
$N, NF y $NF, awk encaja muy bien en tareas basadas en campos como seleccionar columnas específicas o procesarlas con condiciones
- Al encadenar fragmentos
cond{action} y agregar BEGIN{} y END{}, se pueden crear pequeños programas que van más allá de filtros simples e incluyen cadenas, números y arreglos asociativos
Cómo se ejecuta awk
awk puede filtrar texto como grep y sed, y además ofrece funciones de programación para procesamientos más complejos
- La entrada puede venir de stdin o de archivos, y por defecto se recorre línea por línea aplicando condiciones y acciones
- Se puede acceder a la línea completa de entrada mediante la variable especial
$0
- El término exacto es record de entrada, pero en este capítulo se explica en términos de líneas
Filtrado y salida predeterminada
- Las expresiones regulares se escriben con la forma
/.../, y para comprobar si una cadena coincide con una regex se usa la sintaxis string ~ /regexp/
- La condición opuesta se expresa como
string !~ /regexp/
- Si se omite la cadena a inspeccionar, el objetivo pasa a ser
$0
- Si solo hay una condición y no hay acción, cuando la condición es verdadera,
$0 se imprime automáticamente
awk '/regexp/' es una forma abreviada de awk '$0 ~ /regexp/{print $0}'
awk '!/regexp/' es una forma abreviada de awk '$0 !~ /regexp/{print $0}'
- En los ejemplos se muestra el flujo básico de filtrado imprimiendo solo las líneas que contienen
at o las que no contienen e
La salida idiomática usando 1
- En una expresión condicional, los números distintos de 0 y las cadenas no vacías se evalúan como verdaderos
awk '1' es una expresión idiomática que da una condición siempre verdadera para cada línea de entrada
- Cuando no hay acción, una condición verdadera implica imprimir
$0, así que awk '1' imprime toda la entrada tal cual
- El resultado es igual a
awk '{print $0}' o simplemente cat
Sustitución: sub y gsub
awk ofrece las funciones sub y gsub para búsqueda y sustitución
sub(/:/, "-") cambia solo el primer : por - en cada línea de entrada
gsub(/:/, "-") cambia todos los : por - en cada línea de entrada
- Ambas funciones reciben como primer argumento la regex a buscar y como segundo la cadena de reemplazo
- Si no se pasa una cadena de entrada aparte, el objetivo predeterminado es
$0
- Cuando la sustitución tiene éxito, la entrada objetivo queda modificada
- El
1 después del bloque de sustitución se interpreta como una condición fuera del bloque, lo que hace que se vuelva a imprimir el $0 ya modificado
awk '{sub(/:/, "-")} 1' produce el mismo resultado que awk '{sub(/:/, "-"); print $0}'
- Incluso si se escribe solo
print, el objetivo de salida predeterminado sigue siendo $0
Criterios para elegir entre grep, sed y awk
- Para filtrado simple por líneas,
grep, que está optimizado para ese uso, puede ser más rápido que sed o awk
- Para tareas de sustitución,
sed puede ser más rápido que awk
- Las funciones entre herramientas no siempre se corresponden 1:1
- Para implementar
grep -o con sed o awk hacen falta más pasos
- La búsqueda recursiva solo la ofrece
grep
- Se puede ver una discusión relacionada en unix.stackexchange: When to use grep, sed, awk, perl, etc
Procesamiento basado en campos
awk se usa con mucha frecuencia especialmente para procesamiento basado en campos
- Por defecto divide la línea de entrada según espacios, y se accede a cada campo con
$N
$2 es el segundo campo
$NF es el último campo
NF es el número total de campos de la línea de entrada actual
- El ejemplo
table.txt muestra de forma compacta el procesamiento por columnas
- Imprimir el segundo campo de cada línea
- Imprimir solo las líneas cuyo último campo sea negativo
- Cambiar
b por B solo en el primer campo
- El archivo de ejemplo está en el directorio example_files
Estructura de un comando de una sola línea
- Un comando típico de una sola línea con
awk tiene la siguiente forma
awk 'cond1{action1} cond2{action2} ... condN{actionN}'
- Si no hay condición, la acción siempre se ejecuta
- Si no hay acción, cuando la condición es verdadera se imprime
$0
- Dentro de un bloque, varias sentencias se pueden separar con punto y coma
;
- Al encadenar varios bloques, en la mayoría de los comandos de una sola línea se pueden expresar acciones según condición sin usar
if explícito
awk '$NF<0' table.txt imprime solo las líneas cuyo último campo es negativo
BEGIN{} se ejecuta antes de leer la entrada
END{} se ejecuta después de terminar todo el procesamiento de entrada
- Los detalles sobre operadores y expresiones condicionales están en gawk manual: Operators, gawk manual: Truth Values and Conditions
Cadenas y números
awk determina el tipo de los valores según el contexto para facilitar soluciones cortas en la CLI
- Los literales de cadena se escriben entre comillas dobles
- Los números pueden escribirse como enteros, punto flotante o notación científica
BEGIN{} también puede usarse al ejecutar un programa de awk sin entrada externa
- Las variables pueden guardar números y cadenas
- Ejemplo:
a=5; b=2.5; print a+b
- Si se ponen cadenas una al lado de la otra, se concatenan
- Las variables no inicializadas se comportan como cadena vacía en contexto de cadena y como
0 en contexto numérico
- Si una cadena se usa en una expresión numérica, se convierte forzosamente a número
- Si la cadena no empieza con un número válido, se trata como
0
- Los espacios en blanco al inicio se ignoran
- Si se concatena una cadena con un número, el número se convierte en cadena
- Más detalles en gawk manual: Constant Expressions y gawk manual: How awk Converts Between Strings and Numbers
Arreglos
- Los arreglos de
awk son arreglos asociativos y funcionan como pares clave-valor
- La clave puede ser un número o una cadena, pero las claves numéricas se convierten internamente en cadenas
- También se pueden usar arreglos multidimensionales
- En el ejemplo se guardan y consultan valores con claves de cadena como
student["id"] y student["name"]
- La existencia de una clave se comprueba con una forma como
"id" in student
- Más detalles en gawk manual: Arrays
Práctica y siguientes pasos
- Este capítulo presenta brevemente las abreviaciones básicas de la sintaxis de
awk, el filtrado, la sustitución, el manejo de campos, la conversión de tipos y los arreglos
- El siguiente capítulo trata sobre expresiones regulares, y las funciones presentadas aquí se seguirán usando en los ejemplos
- Para familiarizarse con la sintaxis, se puede experimentar cambiando los números de campo o probando otras entradas, como campos negativos o de punto flotante
- La app TUI para práctica interactiva se puede instalar desde el repositorio AwkExercises, y su uso está en app_guide.md
- Todos los ejercicios están reunidos en Exercises.md, y las soluciones están en Exercise_solutions.md
1 comentarios
Opiniones de Hacker News
Me gusta awk y lo uso con bastante frecuencia; uno de sus usos principales para mí es como un sed con estado.
Por ejemplo, para hacer match solo cuando una línea aparece después de cierta línea anterior, lo que resulta útil al crear linters temporales.
Hace poco hice una verificación para encontrar archivos de migración donde se hubiera incluido un
CREATE INDEXque podría ser problemático en tablas muy grandes, sinCONCURRENTLY; como las sentencias SQL pueden abarcar varias líneas, un simple match no bastaba.awk permite seguir estado a lo largo de varias líneas, como “estoy dentro de una sentencia create” o “estoy creando un índice”, así que un script armado un poco a la rápida lleva más o menos un año funcionando bien.
Si quieres imprimir la línea actual solo cuando la línea anterior coincide con cierto patrón, puedes hacerlo con algo como
sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'.Ejemplo:
echo -e "PREV\nCURR\nCURR\nCURR\nPREV\nRED" | sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'imprime soloCURR.Esto usa el hold buffer de sed; con
-nse desactiva la salida por defecto y conpse imprimen solo las líneas necesarias.xintercambia la línea actual con el hold buffer, y dentro del bloque/PREV/ { ... }se intercambia de nuevo y se imprime solo si la línea actual contieneCURR.El último
xsirve para volver a dejar todo como estaba en caso de matches superpuestos.Claro que es muy probable que un script en awk sea mucho más simple y directo, pero también se puede hacer así con sed.
Quizá habría sido mejor invertir ese tiempo en awk, pero el tutorial de https://www.grymoire.com/Unix/Sed.html es tan bueno que casi todo lo que sé sobre sed lo aprendí ahí.
Si estás leyendo este hilo, probablemente también te interese el “Ask HN: Share a shell script you like” de hace 2 semanas.
Tuvo 78 comentarios, así que no despegó tanto como esperaba, pero vale la pena revisarlo: https://news.ycombinator.com/item?id=37112991
También hay una del año pasado: https://news.ycombinator.com/item?id=32467957 (374 puntos, 294 comentarios)
Mantengo un pequeño interés en Awk, junto con Lisp y otros, y en 2022 hice cppawk: https://www.kylheku.com/cgit/cppawk/about/
cppawk amplía Awk con capacidades de preprocesamiento.
Tiene macros de bucle que admiten varias cláusulas; se pueden combinar cláusulas para crear iteraciones paralelas o sobre el producto cartesiano, y también admite extensiones de usuario.
Puedes definir una cláusula nueva escribiendo 5 macros simples.
Si usas Awk, podría resultarte útil; está documentado en varias páginas man y cuenta con pruebas unitarias que corren en gawk y mawk.
Tal vez se me nota la mentalidad de administrador de sistemas de la vieja escuela, pero no veo muy bien cuál es la ventaja de awk frente a escribir lo mismo simplemente en Perl.
He visto muchos scripts de shell horribles hechos por administradores de sistemas junior, y cada vez pensaba: “la parte de procesamiento de texto habría quedado mucho más limpia con Perl”.
Para mí, awk es uno de los pocos lenguajes que puedo dejar de usar durante meses y volver a agarrarle la mano en 10 minutos.
Tiene algo de intuitivo y encaja naturalmente bien con las herramientas comunes de línea de comandos.
Pero este artículo trata de comandos de una línea cortos y temporales, y en esos casos sed/awk son mejores que Perl.
Si ya sabes Perl, no hace falta que aprendas más herramientas; puedes seguir usando Perl.
Como lenguaje en sí es “más o menos”, pero lo suficientemente usable.
Por supuesto, con Perl también puedes hacer todo eso, pero tienes que escribir tú mismo el boilerplate que awk te da gratis.
Para mí, las ventajas del lenguaje Perl no son tan grandes como para abandonar awk, y como uso awk principalmente para procesamiento de datos y transformaciones puntuales de archivos más que para “scripting”, no echo de menos la profundidad de Perl.
Si necesito algo más profundo, me voy a otro lado.
Como la generación actual no aprendió Perl, parece estar redescubriendo la idea del procesamiento de streams con awk.
awk fue una gran idea para fines de los años 70, una innovación real que valía la pena imitar.
Luego Perl la imitó y la superó, pero fue olvidado, así que ver cómo se redescubre awk resulta un poco incómodo.
Una de las cosas menos conocidas de gawk es que normalmente viene con extensiones útiles incluidas.
Puedes acceder a funciones como
readdir(),ord(),chr(),gettimeofday()ysleep().https://www.gnu.org/software/gawk/manual/html_node/Extension-Samples.html
Los comandos de una sola línea de awk son realmente potentes
La pregunta difícil es si vale la pena invertir en programación awk más compleja
Si la tarea de procesamiento exige lógica compleja, awk sí la ofrece, pero lo hace al estilo de los primeros tiempos de la computación: breve y críptico
Pero si eliges una alternativa moderna, también trae su propia carga, en especial puede no ser tan intuitiva como pandas e incluso puede generar problemas de rendimiento
Ni siquiera siento que haya buen soporte para un archivo personal de funciones comunes, y tampoco parece haber una forma adecuada de traer bibliotecas de terceros
Cuando empiezo a necesitar funciones auxiliares y a dividir en varias líneas, normalmente me paso a Python
Y luego el programa termina siendo 2 o 3 veces más grande, y suspiro
Ruby es una gran alternativa a awk, pero si tus compañeros de trabajo no conocen Ruby, es difícil esperar que lo mantengan
Hoy en día, si pegas esos comandos difíciles de leer en una IA, te los explica bastante bien paso a paso, así que se volvieron más aceptables
Aun así, por la posibilidad de hacer pruebas unitarias, prefiero unas cuantas líneas de Python, aunque para trabajos rápidos de transformación de datos a veces ni siquiera hacen falta pruebas unitarias
Se publicó una nueva versión del ebook “CLI text processing with GNU awk”
Permite aprender paso a paso el comando
GNU awk, desde nivel principiante hasta avanzado, con cientos de ejemplos y ejercicios; cubre en profundidad el procesamiento de campos, filtrado, procesamiento de varios archivos, soluciones que dependen de varios registros, comparación de registros y campos entre archivos, búsqueda de duplicados manteniendo el orden de entrada, expresiones regulares, etc.PDF/EPUB: https://learnbyexample.gumroad.com/l/gnu_awk (gratis hasta el 31 de agosto de 2023)
Versión web: https://learnbyexample.github.io/learn_gnuawk/
Fuente en Markdown y archivos de ejemplo: https://github.com/learnbyexample/learn_gnuawk
App TUI interactiva para practicar: https://github.com/learnbyexample/TUI-apps/blob/main/AwkExercises
Como paquete, el conjunto de comandos de una línea para grep, sed, awk, perl y ruby Magical one-liners cuesta $5: https://learnbyexample.gumroad.com/l/oneliners/new_awk_release, y el paquete completo de 13 ebooks cuesta $12: https://learnbyexample.gumroad.com/l/all-books/new_awk_release
Cualquier feedback ayuda mucho, ya sean erratas, errores de código, partes que funcionaron bien o que no funcionaron tan bien, etc.
Como discusiones anteriores están https://news.ycombinator.com/item?id=15549318 y https://news.ycombinator.com/item?id=22758217
Puede ser lo suficientemente útil como para pagar $5 o $15, pero también podría no diferir de lo que ya tengo guardado en mi archivo personal de comandos de una línea, así que me cuesta pagar solo para comprobarlo
Me pregunto si la versión web seguirá siendo gratuita después del 31 de agosto
Hace tiempo hice un golf de dibujar un corazón con AWK: https://gist.github.com/auselen/906a53b47a7d616b080dbef85eb8f776
El 99.9% de mi uso de awk es para dividir líneas descartando espacios en blanco consecutivos
Ej.:
echo "key: value" | awk '{print $1}'Me gustaría que hubiera una alternativa más simple
Está hecho en Nim, pero puede que no sea una gran barrera, y las otras herramientas dentro de
bu/también podrían interesarcut:echo "key: value" | cut -wf 2Aunque es discutible si realmente es más simple
Revisé y GNU cut no tiene
-w, así que es exclusivo de BSDAl final parece mejor usar awk
Ambos son alternativas a cut/awk y también soportan separación basada en expresiones regulares, pero, si no recuerdo mal, no eliminan espacios al inicio/final
Mi herramienta https://github.com/learnbyexample/regexp-cut es parecida a
cut, usaawkpara ofrecer separación basada en expresiones regulares, índices negativos, etc., y gracias al comportamiento predeterminado de awk también maneja espacios al inicio/finalecho "key: value" | awk '{print $2}'Hace un tiempo escribí en awk un script diff2html que había portado desde bash y, por razones obvias, fue mucho más rápido.
awk también era mucho más fácil de leer que el script en bash, y pude aprender el lenguaje, depurarlo, entender los bugs y corregirlos en una sola noche.
No sé si era la forma idiomática de hacerlo en awk, pero me pareció un lenguaje realmente bueno.
https://github.com/berry-thawson/diff2html/blob/master/diff2html.sh