4 puntos por GN⁺ 2023-08-29 | 1 comentarios | Compartir por WhatsApp
  • 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

 
GN⁺ 2023-08-29
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 INDEX que podría ser problemático en tablas muy grandes, sin CONCURRENTLY; 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.

    • Algún día tendré que aprender awk, pero mientras tanto aprendí funcionalidades basadas en estado más profundas de sed.
      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 solo CURR.
      Esto usa el hold buffer de sed; con -n se desactiva la salida por defecto y con p se imprimen solo las líneas necesarias.
      x intercambia 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 contiene CURR.
      El último x sirve 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í.
    • Sería bueno si pudieras compartir un ejemplo de cómo seguiste el estado de SQL con awk.
  • 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

  • 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”.

    • Si estás en un entorno donde Perl siempre está disponible, ese comentario tiene bastante sentido.
      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.
    • Si comparara Awk y Perl para scripts, preferiría Perl o Python.
      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.
    • La fortaleza de Awk es el bucle de lectura que te da gratis, la separación de campos y el modelo de matching por patrones/condiciones.
      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.
    • No hay ninguna ventaja técnica en particular; las capacidades de Perl fueron intencionalmente algo cercano a un superconjunto de awk.
      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.
    • No lo digo con sarcasmo: me da curiosidad por qué Perl sería mejor que Python para scripts.
  • 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() y sleep().
    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

    • Para mí, el gran problema son las bibliotecas
      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
    • Antes de ChatGPT no me gustaban los comandos de una sola línea crípticos en awk/sed
      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

    • Me da curiosidad cuánta gente realmente paga por materiales como Magical one-liners, y si alguna vez probaron un modelo de “mirar primero y luego elegir pagar”
      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
    • Se nota muy bien que le dedicaron mucho trabajo, y en especial me gusta la aplicación TUI
      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

    • Vale la pena considerar https://github.com/c-blake/bu/blob/main/doc/cols.md
      Está hecho en Nim, pero puede que no sea una gran barrera, y las otras herramientas dentro de bu/ también podrían interesar
    • También se puede hacer con cut: echo "key: value" | cut -wf 2
      Aunque es discutible si realmente es más simple
      Revisé y GNU cut no tiene -w, así que es exclusivo de BSD
      Al final parece mejor usar awk
    • También vale la pena mirar https://github.com/sstadick/hck y https://github.com/theryangeary/choose
      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, usa awk para ofrecer separación basada en expresiones regulares, índices negativos, etc., y gracias al comportamiento predeterminado de awk también maneja espacios al inicio/final
    • Probablemente querías decir echo "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