.. _awk-vademecum: .. index:: awk :ref:`awk ` en una línea ============================= :ref:`awk ` es todo un lenguaje de propósito general orientado al tratamiento de textos, pero lo habitual es que se use dentro de *scripts* de la *shell* de forma muy sucinta como alternativa o complemento a :ref:`cut `, :ref:`sed ` y :ref:`grep `. Por ello, dedicamos este epígrafe a explicar cuáles son los modos de uso más útiles relativos a esta función. La mayoría ya están explicados en el epígrafe que se le dedicó al presentar la manipulación de textos, pero procuraremos aquí ser más sistemáticos. .. warning:: :command:`awk` es más extenso y complicado de lo que aquí trataremos. De hecho, dispone de estructuras de control y pueden llegar a programarse con él manipulaciones bastante complejas. Principios de funcionamiento ---------------------------- #. Lee un documento registro a registro y, dentro de cada registro, distingue campos. En principio un registro se corresponde con una línea y cada campo con el conjunto de caracteres de no-espaciado que está separado del anterior y el posterior por uno o más caracteres de espaciado\ [#]_. #. Cada registro puede manipularse y puede generarse a partir de él una o ninguna salida. En este aspecto, actúa del mismo modo que :ref:`cut `, :ref:`grep ` o :ref:`sed `. #. Dispone de una serie de variables predefinidas que pueden usarse para su programación. Las más socorridas son: *NR* Almacena el número de registro que está siendo procesado. Obviamente aumenta su valor en 1 cada vez que se pasa al siguiente registro. Para el primer registro el valor es **1** (y no **0**). *NF* Es el número de total de campos que contiene el registro que se está procesando. *$0* Contenido completo del registro en procesamiento. *$1*, *$2*, etc. Contenido individual de cada campo del registro en procesamiento. Todas (y también *$0*) son de lectura y escritura, por lo que puede alterarse su contenido. *RS* Contiene el separador de registros. Por defecto, es el cambio de línea, de ahí que cada registro se corresponda con una línea. Sin embargo, si alteramos el valor, podemos hacer que :command:`awk` interprete un registro como otra cosa. Cuando no es un único caracter, sino una cadena, la cadena se interpreta como una expresión regular. *ORS* Contiene el separador de registros para la salida. Cuando :command:`awk` acaba de manipular un registro e imprime la salida correspondiente, añade al final el valor de esta variable. Por defecto es el cambio de línea. *FS* Contiene el separador de campos. Al igual que *RS* admite como valor una expresión regular. Su valor predeterminado es :kbd:`\\s+`. *OFS* Contiene el separador de campos para la salida. Por defecto, es el caracter de espacio. Manipulaciones de una línea --------------------------- #. La más sencilla es hacer que :command:`awk` haga de :ref:`cat `:: $ awk '{print $0}' fichero.txt Lo interesante es ver que las instrucciones aplicables a cada registro se introducen dentro de un bloque :kbd:`{}`. En nuestro caso, lo unico que hacemos es imprimir el contenido del propio registro. #. Avancemos un poco más emulando :code:`cat -n`:: $ awk '{print NR, $0}' fichero.txt De esta línea es interesante notar que hemos separado el número de registro, del contenido del mismo mediante una coma. Esto implica que en la salida se separen ambas variables mediante *OFS*, que como no lo hemos redefinido es el espacio. Una variante de lo anterior, podría ser esta:: $ awk '{print NR ":", $0}' fichero.txt En este caso añadimos después del número de registro el carácter "*:*". Al no haber usado nada para separarlos, en la salida se yuxtapondrá el número de registro al carácter "*:*". #. Imprimimos el primero y el último campo de cada línea:: $ awk '{print $1, $NF}' fichero.txt Obsérvese que, para imprimir el último campo, nos ha bastando con *$NF*. #. Listamos los nombres de usuarios existentes:: $ getent passwd | awk -F: '{print $1}' En este caso necesitamos alterar el contenido de *FS* para lo cual existe específicamente una opción. También puede usarse ``-v`` que sería la forma general de pasar valores a los variables:: $ getent passwd | awk -v FS=: '{print $1}' Si son varias las variables, basta con repetir varias veces la opción ``-v``. #. Ídem, pero escribimos los nombres en mayúsculas:: $ getent passwd | awk -v FS=: '{print toupper($1)}' La utilidad real de esto es muy reducida, pero nos sirve para ilustrar cómo :command:`awk` dispone de funciones que permiten presentar un contenido modificado. Hay `muchas funciones para la manipulación de cadenas `_. #. Filtrar registros: mostrar los usuarios cuya *shell* sea :command:`bash`:: $ getent passwd | awk -F: '$NF == "/bin/bash" {print $1}' La forma de hacerlo es incluir la condición, tal cual, antes del bloque. Si nuestra intención es mostrar toda la información de esos usuarios, la solución a la vista de la anterior es trivial:: $ getent passwd | awk -F: '$NF == "/bin/bash" {print $0}' Ahora bien, cuando se introduce una condición y no se especifica cuál es la acción, se sobreentiende que esta es mostrar el registro. Por tanto, podríamos haber simplificado a:: $ getent passwd | awk -F: '$NF == "/bin/bash"' #. Filtrar registros: mostrar sólo los usuarios cuyo nombre empieza por "u":: $ getent passwd | awk -F: '$1 ~ /^u/ {print $1}' La novedad es que usamos una expresión regular para lo cual necesitamos emplear el operador "``~``" y encerrar la expresión entre barras. Una variante de lo anterior podría haber sido:: $ getent passwd | awk -F: '$0 ~ /^u/ {print $1}' o de forma más simple:: $ getent passwd | awk -F: '/^u/ {print $1}' porque, cuando no se expresa con qué se compara, se sobreentiende que es el registro completo, o sea, *$0*. #. Mostrar la información *gecos* de un usuario cuyo nombre tenemos definido fuera de :command:`awk`, es decir, en el *script* de la *shell* que usa :command:`awk`. Para esta tarea podemos usar dos estrategias: * Pasar la variable con ``-v``:: $ USUARIO=pepito $ getent passwd | awk -F: -v USU=$USUARIO '$1 == USU {print $5}' * Hacer que la *shell* sustituya directamente en el código de :command:`awk`:: $ USUARIO=pepito $ getent passwd | awk -F: '$1 == "'$USUARIO'" {print $5}' #. Aplicar distintos filtros a distintos bloques: Ya se ha visto que al aplicar un filtro de la manera antes expuesta, las líneas que no cumplen el filtro desaparecen. Sin embargo, :command:`awk` permite definir distintos bloques, de manera que cada registro aplicará todos aquellos bloques con los que cumpla. Para ilustrarlo supongamos que queremos poner "coleguitas" (con gid 110) como grupo principal de todos los usuarios que empiezan por "u", y no hacer nada con el resto. La siguiente orden generaría un nuevo :file:`/etc/passwd` que cumple con ello:: $ awk -F: -v OFS=: '/^u/ {$4=110; print $0} /^[^u]/' .. note:: Por supuesto, :command:`awk` posee estructuras condicionales (``if``) que pueden usarse dentro de un bloque (como también tiene bucles, p.e.). Pero su uso implica líneas demasiado largas que son difíciles de leer por lo que quedan fuera de esta ridícula guía. Por ejemplo, lo anterior se podría haber resuelto así:: awk -F: -v OFS=: '{if(/^u/) $4=110; print $0}' /etc/passwd que en este caso particular es adminisible, pero no es lo habitual. Sabores ------- Hay tres versiones principales de :command:`awk`\ [#]_: #. :command:`nawk`, que es la versión mantenida por `Brian Kernighan `_, coautor del *awk* original. Es la usada por las distribuciones *BSD* (incluido *Mac Os X*). #. :command:`mawk`, que es una versión optimizada para ser rápida. Es la que trae de serie *debian*. #. :command:`gawk`, que es la versión del proyecto :abbr:`GNU (GNU is Not Unix)`. Incluye muchas extensiones inexistentes en las dos versiones anteriores. Para una comparación de las versiones y en qué grado soportan el estándar *POSIX*, consulte `esta entrada en reddit `_. .. rubric:: Notas al pie .. [#] Esta es una diferencia significativa con :ref:`cut ` para el cual los campos se separan con una y solamente una tabulación. :ref:`awk `, en cambio, separa que los campos por espacios o tabulaciones y en una cantidad arbitraria. Por ello, es bastante más adecuado para manipular un fichero como :file:`/etc/fstab`. .. [#] Además de la mini implementación de :command:`busybox`.