El universo Python es fantástico, está en continua expansión y su naturaleza abierta hace que se nutra de las mejores ideas con independencia de si estaban ya implementadas en otros lenguajes.
Precisamente de una de estas grandes genialidades nos ocupa: las strings interpolation traducido a Python como F-strings. Un componente core presente a partir de la versión 3.6, tan simple como potente y efectivo, sin duda, otro argumento para convencer a los rezagados de Python 2.
Comentaremos la PEP-0498, enumeraremos las múltiples ventajas que nos aportan e intentaremos discernir las posibles pitfalls ¿nos pueden meter en algún lio?, ¿merece la pena migrar todo nuestro software? ¿Nuestros programas pueden verse afectados en cuando a su rendimiento?
Finalmente nos ensuciaremos las manos y pondremos ejemplos (algún jupyter notebook caerá) de los diferentes casos de uso comparándolos con las alternativas previas. ¡Acérquense! ¡Dejen que su código disfrute de una claridad sin precedentes!.
Historia de un brujo Sysdadmin que vivía en el Reino del lejano Backend, triste y enclaustrado entre terminales, conjurando hechizos en Perl y awk, hasta que un buen día (¿o malo quizás?) se le encomendó la noble misión de pregonar en los Siete Reinos HTML los datos que emanaban de las mazmorras SQL.
Solo una gran magia podría satisfacer tan alta causa: Python.
Tricks. Pequeños trucos para mejorar nuestro proyecto.
Es necesario aplicarlas en orden, desde la 01 a la 06, para observar cómo va creciendo y transformándose la aplicación mediante cambios incrementales, conservando las modificaciones previas en BD.
Finalmente podremos hacer checkout al master que contiene el código de la demo completa.
ATENCION: Material complementario. NO es necesario instalar la demo para seguir la charla
Introducción
Me llamo Juan Diego, soy Sysadmin y hasta hace cinco meses no sabía que era HTML .
Bueno … quizás esta última afirmación sea un poco exagerada, para ser honesto había usado templating (jinja2) para generar mails bonitos y experimentado mínimamente con Flask.
¿Cómo me metí en este embolao?
Me ofrecí voluntario OMG … lo cierto es que llevaba tiempo con inquietudes web pero nunca había dispuesto del tiempo necesario crear un side Project, así que cuando laboralmente se me se planteo la necesidad de elaborar una web app la ocasión la pintaron calva .
Como suele pasar siempre cuando uno, o al menos yo, se encuentra, sin experiencia, ante una tarea de estas dimensiones. Se falla, se falla bastante .
El objetivo este post es claro y responde a una pregunta:
¿Cómo puedo ayudar a alguien que ya programe en Python pero sea un completo novato en el mundo web a comenzar con Django?
La respuesta es simple: compartiendo mi experiencia, lo que me sirvió y lo que no.
ATENCION: Este texto NO es un tutorial.
¿Qué Framework escojo?
La respuesta es obvia y la selección natural:
Hecho en Python: en mi opinión el mejor lenguaje para administrar sistemas.
Fiable: GitHub Starts, comunidad, webs en producción.
models.py: Mi portal debía hacer uso de modelos (¿?) con persistencia en BBDD y Django como ORM es una pasada.
Extensible: ¿os habéis dado cuenta que hablo de portal? … pues este también fue un punto fundamental ya que no solo se me solicito la programación de una aplicación, además se deseaba que el sistema fuera extensible, es decir que se pudieran acoplar nuevas apps aprovechando una infraestructura común (autenticación, plantillas, etc.) …. el famoso po ya que.
Caí en la tentación e intenté salir por la vía rápida, instale aplicaciones y boilerplate como un loco, mi objetivo era claro intentar encontrar el santo grial, un código que me lo diera todo (o mucho) hecho.
Descubrí Awesome Django(highly recommended) y probé casi todos (Wagtail, Django-CMS, Mezzanine)
En ocasiones, raras, conseguí que alguno funcionara , pero lo que hacía por debajo era magia negra para mí y eso me imposibilitaba poder adaptar el CMS a mis necesidades, el zen Django todavía estaba muy lejos de mi espíritu .
Así que paré, reflexioné y decidí empezar por los basics: concretamente los tutoriales de Django Girls y Mozilla que os recomiendo fervientemente (en ese orden).
Mi conclusión: Para ser productivos con este framework debemos comprender que se está moviendo under the hood.
El entorno de desarrollo
El mínimo exigible es usar virtualenv, con esto evitaremos al menos romper cosas.
Pero ¿Qué pasa con la base de datos? ¿y al subir a producción?
Clonar un repositorio y lanzar un comando… ya tenemos nuestra web levantada con su proxy-server y su BD ¿no resulta mágico? ¡Pues esa es la idea!
Mis herramientas favoritas para servir a este objetivo son Git y Docker. El código del repositorio ilustra cómo usar diferentes técnicas (Dockerfile, docker-compose) para levantar el entorno de forma automatizada.
¿Qué motor de BBDD usaremos?
Para hacer los primeros experimentos nos vale SQLite, para entornos productivos cualquiera de las otras tres opciones: Oracle, MySQL o PostgreSQL.
Como administrador de BBDD esta última me parece la solución opensource mas óptima.
El settings.py y nuestro .env
El settings.py contiene configuración global de nuestro proyecto, podríamos verlo como el profile que se carga antes de ejecutar un script bash, es absolutamente crítico.
La regla fundamental es ocultar el valor de las variables que escondan secretos, una de las formas de hacerlo es esconderlas en un archivo .env que, por supuesto, NO DEBE ESTAR VERSIONADO.
Para acceder estos valores me ha sido realmente útil el módulo Decouple.
Otra punto a tener en cuenta es que Django dispone de un módulo para acceder a las variables fijadas en settings por lo que este fichero puede ser un buen lugar para setear ciertos valores a los que deseemos acceder desde cualquier punto del proyecto.
Como último consejo añadir que la librería DJ Database URL nos permite usar la configuración en url de nuestra BD.
Todo funcionaba de acuerdo a lo previsto pero se me planteo un problema a la hora de añadir una nueva aplicación manager.
Mi objetivo era el siguiente: Deseaba que el proyecto tuviera un home común donde se mostraran las aplicaciones disponibles al usuario en función de sus permisos.
¿Dónde incluyo este home?
¿En account?: no, se trata de separar funcionalidades y esta aplicación tiene un propósito único especifico que es la gestión de usuarios.
¿En metrics?: no way, esto nos obligaría a duplicar el código en manager, o lo que es peor usar una vista de metrics.
Finalmente opte por una de las soluciones más extendidas y que se basa en la creación de una aplicación core, donde agrupar precisamente todas las funcionalidades comunes que puedan ser llamadas desde cualquier otra aplicación, quedando la estructura así:
La vista home, se renderizará a partir de la plantilla core ¿make sense?
De esta forma puedo añadir aplicaciones de forma indefinida aprovechando la infraestructura común expuesta por ACCOUNT y CORE.
Igualmente me permite trabajar independientemente en cualquier otra aplicación sin temor a romper nada, si no tocamos el código compartido todo seguirá funcionando lo que facilita el desarrollo paralelo a múltiples miembros del equipo.
PRODUCTIVITY BOOST
El enrutamiento
Es uno de mis mecanismos favoritos del Framework y se basa en expresiones regulares.
Cuando llamamos a una url, Django en primer lugar carga el urls.py de la raíz del proyecto y a partir de ahí tratara de resolverla.
¿Que pasa si introducimos nuestro dominio a secas?
Django recorrerá las cinco regex que tenemos parametrizadas, determinará que la única que cumple el patrón es la primera, donde le indicamos que cargue las urls de la aplicación core:
En este caso solo tenemos un patrón contemplado la cadena vacía, que enrutaremos hacia la vista home del proyect.
TIP La idea aquí es ser lo más modular posible, cada app gestionara sus propias urls intentando que la del proyecto raíz quede lo más simple y limpia posible.
Modelos y vistas …¿Dónde está el controlador?
Para comprender Django es fundamental entender cómo funciona su MVC:
Django parece ser un framework MVC, pero ustedes llaman al Controlador «vista», y a la Vista «plantilla». ¿Cómo es que no usan los nombres estándares?
En nuestra interpretación de MVC, la «vista» describe el dato que es presentado al usuario. No es necesariamente cómo se ve el dato, sino qué dato se muestra. La vista describe cuál dato ve, no cómo lo ve. Es una distinción sutil.
Entonces ¿donde entra el «controlador»? En el caso de Django, es probable que en el mismo framework: la maquinaria que envía una petición a la vista apropiada, de acuerdo a la configuración de URL de Django.
Si busca acrónimos, usted podría decir que Django es un framework «MTV», esto es «modelo», «plantilla» y «vista». Ese desglose tiene mucho más sentido.
En la práctica nos basta con saber:
Diseñar los modelos.
Programar las vistas y su template.
Un modelo lo podríamos definir como una clase cuyos objetos instanciados están dotados de persistencia en BD.
Un modelo se traduce en una tabla cuyos campos se mapean con las variables de la clase modelo.
Diseñarlos requiere pensar detenidamente en los datos que se desean representar.
Para ahorrarnos curro, podemos tomar como ejemplo el modelo User.
Los modelos además pueden relacionarse entre sí, mediante campos / variables comunes (por ejemplo mediante ForeignKey).
No necesitamos SQL, ya que nos proporcionan una API query pythonica.
TIP Por defecto Django crea las tablas mediante las migrations, pero podemos establecer que el modelo no sea manejado y de esa forma podremos incorporar una tabla preexistente al ecosistema, es una buena forma de integrar datos provenientes de aplicaciones externas, y se nos proporcionan utilidades para inspeccionar el modelo.
TIP Campo único, Django se lleva mal con los modelos formados por pks en múltiples campos.
Las vistas, por norma general, se ocupan de representar ciertos datos provenientes de nuestros modelos.
En la estructura del proyecto ya hemos adelantado que lo ideal es mantener la funcionalidad común fuera del scope de las aplicaciones aislables, por lo que la primera de medida debe ser sacar la gestión de usuarios, en el ejemplo que estamos tratando se sitúa incluso en diferente nivel.
TIPfundamental es SIEMPRE heredar del Abstractuser, nos permitirá olvidarnos del modelo Profile. Esto debe hacerse siempre en la creación del proyecto ya que la migración de el modelo de User es muy complicada.
TIP usar las auth views que ya nos viene de serie y permiten avanzar rápido en el proyecto sin perjuicio de una customización posterior.
Django Rest Framework (o GraphQL)
Otra de mis grandes pifias.
Mi primera aplicación se basaba en extraer un contexto en el views.py y enviársela al template para que renderizara los resultados.
Para páginas sencillas el resultado era el esperado pero la cosa se complica si tienes que representar miles de datos.
La plantilla se convierte en un infierno de llaves y todo tiende a romperse con facilidad.
There must be a better way.
TIPsepara el Backend (API) del Frontend.
La idea es simple, nuestro proyecto expondrá una API, en nuestro caso REST que será invocada directamente desde nuestras plantillas.
Al igual que ocurre con la separación de aplicaciones en la estructuración del proyecto, si separamos el Backend del Frontend podremos dividir el trabajo de forma más eficiente, además la API podrá ser integrada por otras aplicaciones por ejemplo móviles.
La tecnología trending es GraphQL y sin duda es el futuro, pero DRF es sin duda el mejor añadido a Django, y para ciertas querys resulta de difícil substitución, un caso claro es el de las métricas donde necesitamos todos los campos y de forma secuencial.
Frontend
Los templates son base del Frontend y la tercera pata del MVT, es donde vamos a escribir nuestro HTML (JS y demás).
Es un sistema diseñado de forma muy inteligente, teniendo en cuenta que Django no está enfocado a la construcción de single-page applications.
Cada app tendrá sus propios templates encargados de renderizar los datos proporcionados por la vista que invoca o extraídos directamente desde la API (si la hemos usado claro).
Se sitúan en la ruta template/app que debe colgar de la carpeta donde esté el código de la aplicación.
Las plantillas del proyecto raíz colgarán del primer nivel.
La herencia nos permite extender (y modificar) la plantilla base que contendrá los elementos básicos y el look and feel de nuestra web, facilitándonos enormemente la tarea de construir una nueva página ya que los elementos comunes ya nos vienen dados (DRY).
Podríamos verlo, construyendo un paralelismo, como la herencia de de una clase Python, podemos heredar todo el código de la clase base, pero también podemos ampliarlo y modificarlo
Respecto al lenguaje poco que mencionar, Django proporciona su propia sintaxis y nos permite expresarnos en el template de modo programático proporcionándonos una enorme versatilidad.
Los tags y filtros nos permiten incorporar código Python y usarlo en nuestras plantillas.
Para visualizar estos conceptos, supongamos este tag:
Andaba yo muy contento con los resultados del trabajo hasta la primera reunión de seguimiento con los usuarios donde la primera pregunta fue, … ¿y esto cómo se ve en el móvil?
¿OLA K ASE?
No se me había pasado por la cabeza esa posibilidad: se veía mal, muy mal.
La cruda (o no) realidad es esta: Si deseas que tu web tenga un interfaz de usuario moderno tendrás que ir más allá de Python y un HTML básico.
Yo me he apoyado fundamentalmente en estos dos elementos:
Bootstrap
Javascript
Bootstrap es un framework web basado principalmente en HTML y CSS que mejora (o hace más vistosos) los elementos que incluimos en nuestros templates, además permite que nuestras páginas sean responsive sin demasiada dificultad (Sistema en grid)
Javascript es un lenguaje que tiene la gran ventaja de poder ejecutarse en el navegador. Existen innumerables librerías .js muchas de ellas super cool, que nos permiten hacer verdaderas viguerías con los datos devueltos por nuestro Backend, no debemos convertirnos en talibanes del lenguaje, JS es una utilidad imprescindible en el Frontend.
Ejemplo:
{%extends'base.html'%}{%loadstatic%}{%blockextrajs%}<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>{%endblock%}{%blockcontent%}<divid="container"class="container"><table><thead><tr><th>Metric</th><th>Tipo</th></tr></thead><tbody><trv-for="metrica in metricas"><td>[[metrica.nombre]]</td><td>[[metrica.long_tipo]]</td></tr></tbody></table></div><script>varv=newVue({delimiters:['[[',']]'],el:'#container',data:{metricas:[]},mounted:function(){$.get('/metrics/api/v1/metricas/',function(data){v.metricas=data;})}})</script>{%endblockcontent%}
Gestión de permisos
Otro de los temas básicos en una aplicación en producción, la infraestructura es común pero los usuarios no deben percibirlo salvo en el look and feel de la pagina, uno solo debe ver lo que debe ver.
Podemos y debemos controlar el acceso a nuestras urls a tres niveles:
TIP Django tiene un control muy fino de permisos por usuario, pero podemos simplificar mucho la gestión usando los grupos como si fueran roles.
Estrategias de Deploy
El manage.py está muy bien para desarrollar, pero los tráficos serios deben ser atendidos desde un servidor web propiamente dicho, mi opción predilecta para producción es NGINX.
NGINX se encargará de servir el contenido estático y mediante un proxyserver redireccionará las peticiones de contenido dinámico a la url levantada por el servidor WSGI.
It will be the perfect tool when there’s no need to inspect the command args, only process name is taken in consideration.
-o, –older-than
Match only processes that are older (started before) the timespecified. The time is specified as a float then a unit. The
units are s,m,h,d,w,M,y for seconds, minutes, hours, days,
weeks, Months and years respectively.
-r, –regexp
Interpret process name pattern as an extended regular expression.
This ps supports AIX format descriptors, which work somewhat like the formatting codes of printf(1) and printf(3).
For example, the normal default output can be produced with this:
ps -eo "%p %y %x %c"
CODE NORMAL HEADER
%C pcpu %CPU
%G group GROUP
%P ppid PPID
%U user USER
%a args COMMAND
%c comm COMMAND
%g rgroup RGROUP
%n nice NI
%p pid PID
%r pgid PGID
%t etime ELAPSED
%u ruser RUSER
%x time TIME
%y tty TTY
%z vsz VSZ
First things first, To Kill a process, we need is its PID, then get how long has it been running and finally the command name and it’s args.
This be accomplished by this format string using the codes mentioned in the table above:
ps -eo "%p>~<%t>~<%a"
NOTE : It’s important to choose a complicated string as separator between our fields >~<, we don’t want to find the same one inside the command name or the args garbling our data.
To process this output let’s compose an awk1 oneliner, step by step.
How to get processes running for + 24h?
In ps man page:
etime ELAPSED elapsed time since the process was started, in the form [[dd-]hh:]mm:ss
So, a dash char in the second field means that the program has been running for at least 24 hours.
Un interface en Go es un tipo, tan simple como eso.
Parce una tontería pero una vez que tenemos claro esto lo demás viene rodado.
Eso si es un tipo especial, especial porque solo puede contener métodos, es decir, las acciones que se podrán ejecutar sobre nuestros tipos, generalmente structs.
Para empezar supongamos un objeto Bici sobre el que definimos un método para establecer la marcha que vamos a usar:
Parece directo… pero este código no es compilable:
./main.go:29: cannot use Moto literal (type*Moto) as type*Bici in assignment
Moto y Bici no tienen nada en común … ¿o sí?
Parece evidente que estos dos objetos comparten ciertas acciones / métodos, en nuestro caso PonMarcha, por lo que podríamos definir un tipo interface que los agrupe:
¡Nuestro código ya compila! Podemos usar una variable tipo Vehiculo porque sabemos que tanto el tipo Bici como el tipo Moto satisfacen su interface.
Esa es la condición sin ecua non: todos los tipos deben implementar todos los métodos del interface, pero no quedan limitados por el, por ejemplo a Moto se le podría añadir la acción para echar gasolina:
func(m*Moto)EchaGasolina(nivelint){m.nivel=nivel}
Seguiría satisfaciendo el interfaceVehiculo pero , OJO, como es lógico, no se podría acceder al nuevo método desde el:
./main.go:36: vehiculo.EchaGasolina undefined (type Vehiculo has no field or method EchaGasolina)
Stringer
Este es el ejemplo clásico del interfaceStringer que nos acercará mucho más a su uso más práctico.
Está definido en el paquete fmt y su código no puede ser más simple:
typeStringerinterface{String()string}
Todo lo que nos importa es: si definimos un objeto que satisfaga este interface podremos aprovecharnos la funcionalidad implementada en print definida como:
The String method is used to print values passed as an operand to any format that accepts a string or to an unformatted printer such as Print.
Un tipo que implemente el interfaceStringer podrá pasarse a cualquier función de impresión de fmt para obtener su representación.
Veámoslo en nuestro ejemplo:
packagemainimport("fmt""strconv")typeBicistruct{marchaint}func(b*Bici)PonMarcha(marchaint){b.marcha=marcha}func(bBici)String()string{return"Bici en marcha número: "+strconv.Itoa(b.marcha)}typeMotostruct{marchaintnivelint}func(m*Moto)PonMarcha(marchaint){m.marcha=marcha}func(mMoto)String()string{return"Moto en marcha número: "+strconv.Itoa(m.marcha)}typeVehiculointerface{PonMarcha(marchaint)String()string}funcmain(){varvehiculoVehiculovehiculo=&Bici{}vehiculo.PonMarcha(14)fmt.Println("Vehículo: ",vehiculo)vehiculo=&Moto{}vehiculo.PonMarcha(2)fmt.Println("Vehículo: ",vehiculo)}
One more thing … English is not my first language so bear with me please .
AWK is a language similar to PERL, only considerably more elegant.
Arnold Robbins
What ??
AWK is a programming language designed for text processing and typically used as a data extraction and reporting tool.
It is a standard feature of most Unix-like operating systems.
awkaward name …
its name is derived from the surnames of its authors – Alfred Aho, Peter Weinberger, and Brian Kernighan.
So awk …
Searchs for lines that contain certain patterns in files or the standard input.
Mostly used for data extraction and reporting like summarizing information from the output of other utility programs.
C-like syntax.
Data Driven: it’s describe the data you want to work with and then what action to do when you find it.
WARNING: Both rules are executed once only, BEGIN before the first input record is read, END after all the input is consumed.
$ echo"hello"| awk'BEGIN{print "BEGIN";f=1}
{print $f}
END{print "END"}'
BEGIN
hello
END
Why grepping if you have awk ??
$ cat lorem_ipsum.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Maecenas pellentesque erat vel tortor consectetur condimentum.
Nunc enim orci, euismod id nisi eget, interdum cursus ex.
Curabitur a dapibus tellus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Aliquam interdum mauris volutpat nisl placerat, et facilisis.
$ grep dolor lorem_ipsum.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
$ awk'/dolor/' lorem_ipsum.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Note: If the action is not given the default action is to print the record that matches the given pattern.
But… how can we find out the first and last word of each line?
Of course grep can, but needs two steps:
$ grep-Eo'^[^ ]+' lorem_ipsum.dat
Lorem
Maecenas
Nunc
Curabitur
Lorem
Aliquam
Isn’t this better ? Yeah, but… HTF does this works?
awk divides the input for your program into Records and Fields.
Records
Records are separated by a character called the record separatorRS. By default, the record separator is the unix newline character \n.
This is why records are, by default, single lines.
Additionally awk has ORSOutput Record Separator to control the way records are presented to the stdout.
RS and ORS should be enclosed in quotation marks, which indicate a string constant.
To use a different character or a regex simply assign it to the RS or / and ORS variables:
Often, the right time to do this is at the beginning of execution BEGIN, before any input is processed, so that the very first record is read with the proper separator.
Another way to change the record separator is on the command line, using the variable-assignment feature.
Examples:
$ awk'BEGIN{RS=" *, *";ORS="<<<---\n"}
{print $0}' lorem_ipsum.dat
Lorem ipsum dolor sit amet<<<---
consectetur adipiscing elit.
Maecenas pellentesque erat vel tortor consectetur condimentum.
Nunc enim orci<<<---
euismod id nisi eget<<<---
interdum cursus ex.
Curabitur a dapibus tellus.
Lorem ipsum dolor sit amet<<<---
consectetur adipiscing elit.
Aliquam interdum mauris volutpat nisl placerat<<<---
et facilisis neque ultrices.
<<<---
$ awk'{print $0}'RS=" *, *"ORS="<<<---\n" lorem_ipsum.dat
Lorem ipsum dolor sit amet<<<---
consectetur adipiscing elit.
Maecenas pellentesque erat vel tortor consectetur condimentum.
Nunc enim orci<<<---
euismod id nisi eget<<<---
interdum cursus ex.
Curabitur a dapibus tellus.
Lorem ipsum dolor sit amet<<<---
consectetur adipiscing elit.
Aliquam interdum mauris volutpat nisl placerat<<<---
et facilisis neque ultrices.
<<<---
Fields
awk records are automatically parsed or separated into chunks called fields.
By default, fields are separated by whitespace (any string of one or more spaces, TABs, or newlines), like words in a line.
To refer to a field in an awk program, you use a dollar $ sign followed by the number of the field you want.
Thus, $1 refers to the first field, $2 to the second, and so on.
IMPORTANT: $0 represents the whole input record.
$ awk'{print $3}' lorem_ipsum.dat
dolor
erat
orci,
dapibus
dolor
mauris
NF is a predefined variable it's value is the number of fields in the current record. So, $NF will be always the last field of the record.
$ awk'{print NF}' lorem_ipsum.dat
8
7
10
4
8
10
FS holds the valued of the field separator, this value is a single-character string or a regex that matches the separations between fields in an input record.
The default value is " ", a string consisting of a single space. As a special exception, this value means that any sequence of spaces, TABs, and/or newlines is a single separator.
In the same fashion that ORS we have a OFS variable to manage how our fields are going to be send to the output stream.
In awk the arrays are associative, each one is a collection of pairs, index – value, where the any number or string can be an index.
No declaration is needed; new pairs can be added at any time.
Index
Value
“perro”
“dog”
“gato”
“cat”
“uno”
“one”
1
“one”
2
“two”
To refer an array:
array[index-expression]
To assign values:
array[index-expression] = value
To check if a key is indexed:
indx in array
To iterate it:
for(var in array){
var, array[var]
}
Using numeric values as indexes and preserving the order:
for(i = 1; i <= max_index; i++){
print array[i]
}
A complete example:
$ cat dict.dat
uno one
dos two
tres three
cuatro four
awk'{dict[$1]=$2}
END{if ("uno" in dict)
print "Yes we have uno in dict!"
if (!("cinco" in dict))
print "No , cinco is not in dict!"
for (esp in dict){
print esp, "->" ,dict[esp]
}
}' dict.dat
Gives you:
Yes we have uno in dict!
No , cinco is not in dict!
uno -> one
dos -> two
tres -> three
cuatro -> four
gensub(regexp, replacement, how [, target]) : Is the most advanced function for string replacing.
And their simpler alternatives:
gsub(regexp, replacement [, target])
sub(regexp, replacement [, target])
Having this file:
$ cat lorem.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Maecenas pellentesque erat vel tortor consectetur condimentum.
Nunc enim orci, euismod id nisi eget, interdum cursus ex.
Curabitur a dapibus tellus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Aliquam interdum mauris volutpat nisl placerat, et facilisis.
We're going to swap the position of the words placed at the left and the right of each comma.
$ awk'{print gensub(/([^ ]+)( *, *)([^ ]+)/,
"\\3\\2\\1", "g")}' lorem.dat
Lorem ipsum dolor sit consectetur, amet adipiscing elit.
Maecenas pellentesque erat vel tortor consectetur condimentum.
Nunc enim euismod, orci id nisi interdum, eget cursus ex.
Curabitur a dapibus tellus.
Lorem ipsum dolor sit consectetur, amet adipiscing elit.
Aliquam interdum mauris volutpat nisl et, placerat facilisis.
Using gensub we capture three groups and then we swap the order.
To illustrate a simpler action let's change dots for commas:
awk'$0=gensub(/\./, ",", "g")' lorem.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
Maecenas pellentesque erat vel tortor consectetur condimentum,
Nunc enim orci, euismod id nisi eget, interdum cursus ex,
Curabitur a dapibus tellus,
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
Aliquam interdum mauris volutpat nisl placerat, et facilisis,
Using gsub alternative:
awk'gsub(/\./, ",")' lorem.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
Maecenas pellentesque erat vel tortor consectetur condimentum,
Nunc enim orci, euismod id nisi eget, interdum cursus ex,
Curabitur a dapibus tellus,
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
Aliquam interdum mauris volutpat nisl placerat, et facilisis,
This option seems better when no group capture is needed.
$ cat lorem.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Maecenas pellentesque erat vel tortor consectetur condimentum.
Nunc enim orci, euismod id nisi eget, interdum cursus ex.
Curabitur a dapibus tellus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Aliquam interdum mauris volutpat nisl placerat, et facilisis.
$ awk'{print $(NF-1)}' lorem.dat
adipiscing
consectetur
cursus
dapibus
adipiscing
neque
Not too much to explain here, NF stores the number of fields in the current record, so NF-1 points to field before last and $(NF-1) will be its value.
02. Replacing a record
Our task, file record substitution, the third line must become:
This not latin
Nothing more simple, just play around NR (number of record).
Code:
$ awk'NR==3{print "This is not latin";next}{print}' lorem.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Maecenas pellentesque erat vel tortor consectetur condimentum.
This is not latin
Curabitur a dapibus tellus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Aliquam interdum mauris volutpat nisl placerat, et facilisis.
Alternative solution to avoid next statement: assign the new line to the complete record $0.
Example:
$ awk'NR==3{$0="This is not latin"}1' lorem.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Maecenas pellentesque erat vel tortor consectetur condimentum.
This is not latin
Curabitur a dapibus tellus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Aliquam interdum mauris volutpat nisl placerat, et facilisis.
03. Place a semicolon at the end of each record
$ awk'1'ORS=";\n" lorem.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit.;
Maecenas pellentesque erat vel tortor consectetur condimentum.;
Nunc enim orci, euismod id nisi eget, interdum cursus ex.;
Curabitur a dapibus tellus.;
Lorem ipsum dolor sit amet, consectetur adipiscing elit.;
Aliquam interdum mauris volutpat nisl placerat, et facilisis neque ultrices.;
As the default RS is the unix break line \n we just need to prefix the semicolon to the Output Record Separator OFS.
$ cat even.dat
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nunc enim orci, euismod id nisi eget, interdum cursus ex.
Lorem ipsum dolor sit amet, consectetur adipiscing elit
$ cat odd.dat
Maecenas pellentesque erat vel tortor consectetur condimentum.
Curabitur a dapibus tellus.
Aliquam interdum mauris volutpat nisl placerat, et facilisis.
The modulo function (%) finds the remainder after division for the current Record Number NR divided by two:
$ awk'{print NR%2}' lorem.dat
1
0
1
0
1
0
As far as we now yet, in awk1 is True and 0False. We redirect our output evaluating this fact.
next requires an special attention, it forcesawk to immediately stop the current record process and pass to next one.
In this way we elude a double condition that would look like this:
We are playing with an intermediate variable used to store the first field value, the we swap its value with the last one, finally we assign last variable to $NF ($NF=last).
09. Traceroute hacking
Having this output:
$ traceroute -q 1 google.com 2>/dev/null
1 hitronhub.home (192.168.1.1) 5.578 ms
2 217.217.0.1.dyn.user.ono.com (217.217.0.1) 9.732 ms
3 10.127.54.181 (10.127.54.181) 10.198 ms
4 62.42.228.62.static.user.ono.com (62.42.228.62) 35.519 ms
5 72.14.235.20 (72.14.235.20) 26.003 ms
6 216.239.50.133 (216.239.50.133) 25.678 ms
7 mad01s24-in-f14.1e100.net (216.58.211.238) 25.019 ms
We need to compute the package travelling total time.
NR>1{ips[$1]+=$2}: The action ips[$1]+=$2 is only executed when the current record number is greater than one NR>1. This is needed to avoid the header.
ips is an array indexed by the ip value (the $1 field), for each key we are going to accumulate in the value of the second field.
Take notice of an important fact, if a key is not present in the array, awk adds a new element to the structure, otherwise is going to update the previous value pointed by that key (as in our example).
The ENDrule is just used to iterate the array by indexes and values.
This code could be rewritten in complete different manner to avoid the use of arrays and preserve the order taken advantage of the sorted IPs file:
lip && lip != $1{print lip,sum;sum=0}: Here we use a var named lip (last-ip). lip && lip != $1 When lip is not null and it's value not equal to the first field (that holds the current ip) the triggered action will be to print lip and sum the total amount of bytes for the last IP. Then we initialize it sum=0.
The trick is clear, every time IP ($1) changes we show the stats of the previous one.
{sum+=$2;lip=$1}: To update the bytes counter sum+=$2 and assign the current IP to lip: lip=$1. It is the last step of our record processing.
The END block is used to print the pending values.
This code preserves the order, but in my opinion, this comes at the expense of significantly increased complexity.
12. Records between two patterns
Our task is two extract the lines between and OUTPUT and END.
$ cat pat.dat
test-3test-2test-1
OUTPUT
top 2
bottom 1
left 0
right 0
page 66
END
test 1
test 2
test 3
This is a classic example used to illustrate how pattern matching works in awk and its associate actions which I dedicated a complete post.
$ awk'/END/{flag=0}flag;/OUTPUT/{flag=1}' pat.dat
top 2
bottom 1
left 0
right 0
page 66
Its based in the flag variable value, it will be True (1) when the starting pattern OUTPUT is found and False (0) when ENDtag is reached.
To avoid an additional step, the action order is very important, if we follow the logic sequence:
$ awk'/OUTPUT/{flag=1}flag;/END/{flag=0}' pat.dat
OUTPUT
top 2
bottom 1
left 0
right 0
page 66
END
Pattern tags are shown through the output.
The reason: after OUTPUT pattern is found the flag gets activated, as the next action depends of this flag the record is printed.
We can avoid this behavior placing the flag activation as the last step of the flow.
To understand how it works one concept must be clear, the ternary operator (subject of an old post).
total will be used to accumulate the divison of the first field $1 by the second $2 that will hold the value given by the ternary operator: 1024 when $2 is equal to kb and 1 if no transformation needed.
ORS = NR%3 ? FS : RS: Finally we use the ternary operator (explained just before) to evaluate the moduloNR%3 result of the division of the current field number NR by three.
If the remainder is True the ORS becomes FS, a blank space, otherwise the RSdefault value will be assigned, the Unix line break \n.
15. FASTA File processing
In bioinformatics, FASTA is a text-based file format.
awk is the perfect tool for this reporting effort, for this example we will use:
awk'/^>/ { if (seqlen) {
print seqlen
}
print
seqtotal+=seqlen
seqlen=0
seq+=1
next
}
{
seqlen += length($0)
}
END{print seqlen
print seq" sequences, total length " seqtotal+seqlen
}' fasta.dat
The first action is tied to the header detection /^>/, that's because all headers stars with > character.
When seqlen is not null its value, that holds the previous sequence length, is printed to the stdout attached to the new header. seqtotal is updated and seqlen initialized to serve the next sequence. Finally, we break further record processing with next.
The second action{seqlen += length($0)} is used to update seqlen summing the total record length.
The ENDrule purpose is to show the unprinted sequence and the totals.
Trick here is to print the previous sequence length when we found a new header.
When we process the first record seqlen has no value so we skip the visualization.
awk'{$1=$1}
/snaps1/ && $NF>0{print;f=1}
f && /Instance/ {print;f=0}' report.dat
For every record the first action is executed, it forces awk to rebuild the entire record, using the current values for OFS3.
This trick allows us to convert a multiple space separator to a single char, the default value for the Output Field Separator.
Let's see this:
$ awk'1' text.dat
one two
three four
$ awk'$1=$1' text.dat
one two
three four
Second action is triggered when the pattern is found and the last field greater than zero /snaps1/ && $NF>0.
awk prints the record and assign a True value to the flag print;f=1.
Last step: when flag is True and instance pattern in the line f && /Instance/, show the line and deactivate flag: print;f=0.
17. Files joiner
Let's suppose two archives:
$ cat join1.dat
3.5 22
5. 23
4.2 42
4.5 44
$ cat join2.dat
3.5
3.7
5.
6.5
We need the records from the first one join1.dat when the first fields are in the second one join2.dat.
Output should be:
3.5 22
5. 23
We can use unix join utility, of course, but we need to sort the first file:
$ join <(sort join1.dat) join2.dat
3.5 22
5. 23
Not needed in awk:
$ awk'NR == FNR{a[$1];next}
$1 in a' join2.dat join1.dat
Let's study the filters and the actions:
NR == FNR: Record Number equal to Record File Number means that we're processing the first file parsed to awk: join2.dat.
The pair action a[$1];next will be to add a new void value to the array indexed by the first field. next statement will break the record processing and pass the flow to the next one.
For the second actionNR != FNR is applied implicitly and affects only to join1.dat, the second condition is $1 in a that will be True when the first field of join1.dat is an array key.
We need a multiple file flow as we studied in our last example:
$ awk-F\:'NR == FNR{g[$3]=$1;next}
$4 in g{print $1""FS""g[$4]}' /etc/group /etc/passwd
To process /etc/group we repeat the NR == FNR comparison then store the name of the group$1 indexed by its ID$3: g[$3]=$1. Finally, we break further record processing with next.
The second condition will target only /etc/passwd records, when the fourth field $4 (group ID) is present in the array $4 in g, we will print the login and the value pointed by the array indexed by the group idg[$4], so: print $1""FS""g[$4].
19. User connections
Users utility output example:
$ users
negan rick bart klashxx klashxx ironman ironman ironman
We're going to count logons per user.
$ users|awk '{a[$1]++}
END{for (i in a){print i,a[i]}}'RS=' +'
rick 1
bart 1
ironman 3
negan 1
klashxx 2
The action is performed for all the records.
a[$1]++: This is the counter, for each user$1 it increments the pointed value (uninitialized vars have the numeric value zero).
In the ENDblock iterate the array by key and the stored value to present the results.