Video: PostgreSQL – Reunión Gultij, enero de 2017

El pasado sábado 21 de enero de 2017 durante la reunión mensual del Gultij, Grupo de Usuarios de GNU/Linux de Tijuana, México, di una presentación sobre PostgreSQL, un sistema manejador de bases de datos objeto-relacional (ORDBMS).

Liga al video en YouTube: https://www.youtube.com/watch?v=xyq6b9wZ0aw

Esperar hasta terminar la entrada estándar

Si uno teclea líneas a la entrada estándar desde el teclado, ésta se va procesando línea por línea:

$ cat -n
hola
     1	hola
mundo
     2	mundo
maravilloso
     3	maravilloso
^D
$

Así, resulta impráctico marcar la salida usando el ratón para copiarlo al portapapeles o simplemente para ver la salida junta. Como alternativas tenemos:

  • Marcar y copiar línea por línea, acumulando el resultado en un editor antes de copiar todo el texto. Muy impráctico.
  • Redirigir la salida a un archivo, pero aún queda pendiente tener que procesar y borrar el archivo después.
  • Usar tac | tac:
    $ tac | tac | cat -n
    hola
    mundo
    maravilloso
    ^D
         1	hola
         2	mundo
         3	maravilloso
    $
    

tac es una instrucción que concatena archivos y los despliega en reversa (LIFO en lugar de FIFO por línea). Por naturaleza, tac se ve en la obligación de leer la entrada estándar completa antes de poder mandar la primera línea de su salida. Si invertimos la entrada dos veces se obtiene el efecto deseado.

No sé hasta cuántas líneas soporte antes de volverse lento. Aquí con 10,000 líneas funciona aún bastante bien.

¿Alguien conoce alguna mejor manera?

Actualización: Dualbus me recomienda sponge de moreutils. Ejemplo:

$ sponge | cat -n
hola
mundo
maravilloso
^D
     1	hola
     2	mundo
     3	maravilloso
$

Según el manual, sponge “absorbe la entrada estándar y la escribe a un archivo”.

postfix_delivery_search.bash

Este script sirve para buscar en los logs de Postfix 2.9.6 la historia de entrega de los correos que cumplan con el from= y el to= que se indiquen en el primer y segundo argumento del script, respectivamente.

#!/bin/bash

# Configuration variables
REQUIREMENTS="gawk grep"
LOGSOURCES="/var/log/mail.info.1 /var/log/mail.info"

# Check requirements
which $REQUIREMENTS > /dev/null 2>&1 || {
    echo The following required programs were not found:
    for R in $REQUIREMENTS; do
        which "$R" > /dev/null 2>&1 || echo "$R"
    done
    exit 63
}

# Check arguments
[ $# -eq 2 ] || {
    echo Usage: "$0" source_address destination_address
    exit 1
}

# These work for Postfix 2.9.6
function get_filter_from() {
    echo 'BEGIN { FS="[ :]+"} /from=<'$1'>/ && !/NOQUEUE/ { print $8 }'
}

function get_filter_to() {
    echo 'BEGIN { FS="[ :]+"} /to=<'$1'>/ && !/NOQUEUE/ { print $8 }'
}

function awniq() {
    gawk '!a[$0]++'
}

FILTER_FROM=$(get_filter_from "$1")
FILTER_TO=$(get_filter_to "$2")

# For each mail ID found in both from= and to= lines...
for ID in $(grep -f <(gawk "$FILTER_FROM" $LOGSOURCES | awniq) \
  <(gawk "$FILTER_TO" $LOGSOURCES | awniq))
do
    echo ====== $ID ======
    grep -- "$ID" /var/log/{mail.info.1,mail.info}
done

Codificación defensiva (era Re: Pleonasmos booleanos)

Cuidado con las vulnerabilidades.

En Pleonasmos Booleanos, publicación del excelente blog Developeando, del cual me considero un ávido seguidor, Mario nos sensibiliza sobre la legibilidad de código. Nos dice que al escribir código debemos tomar en cuenta que no solo escribimos para la maquina. Concuerdo con él.

Sin embargo, hay que considerar qué pasa cuando lo que parece que es código redundante, realmente no lo es. Qué pasa si tenemos un código donde sólo debemos lanzar un misil cuando explícitamente no está prohibido:

function deboLanzarMisil(statusProhibicion) {
    result = false

    if (statusProhibicion == false)
        result = true;

    return result;
}

y lo que parece código redundante se legibiliza así:

function deboLanzarMisil(statusProhibicion) {
    return !statusProhibicion;
}

o incluso así:

function deboLanzarMisil(statusProhibicion) {
    if (!statusProhibicion)
        return true;

    return false;
}

En ambos casos habríamos introducido una vulnerabilidad (o bueno, de bug para arriba) que se presenta cuando statusProhibicion es null. En el primer código:

> deboLanzarMisil(true)
false
> deboLanzarMisil(false)
true
> deboLanzarMisil(null)
false

En ambos ejemplos de código legibilizado:

> deboLanzarMisil(true)
false
> deboLanzarMisil(false)
true
> deboLanzarMisil(null)
true

El código que estoy usando como ejemplo de lo que es supuestamente ilegible en realidad sigue un patrón de programación defensivo. En esta técnica:

  1. Se inicializa una variable a un valor seguro o valor por defecto.
  2. Sólo se modifica el valor bajo validaciones estrictas.

Claro que, aún cuando el siguiente código sigue esta técnica, habría sido inseguro, pero eso es porque se violó la regla #2:

function deboLanzarMisil(statusProhibicion) {
    result = false

    if (!statusProhibicion)
        result = true;

    return result;
}

El punto es que la única manera de corregir un potencialmente vulnerable return !x manteniendo la legibilidad hubiera sido usar más de una línea de código o invertir la lógica de la función.

El código que usa Mario para ejemplificar el caso de ilegibilidad resulta ser funcionalmente equivalente al código legibilizado y no son vulnerables ante una entrada null. Sin embargo consideremos los dos siguientes códigos, que no son funcionalmente equivalentes.

Este código es seguro:

function deboLanzarMisil(statusProhibicion) {
    if (statusProhibicion == false)
        return true;
    else
        return false;
}

> deboLanzarMisil(true)
false
> deboLanzarMisil(false)
true
> deboLanzarMisil(null)
false

Este otro código, aparentemente equivalente, resulta inseguro ante una entrada null:

function deboLanzarMisil(statusProhibicion) {
    if (statusProhibicion == true)
        return false;
    else
        return true;
}

> deboLanzarMisil(true)
false
> deboLanzarMisil(false)
true
> deboLanzarMisil(null)
true

Al final todo depende del lenguaje de programación. En SQL, por ejemplo, la lógica es distinta: NULL <> NULL da NULL y NULL == NULL da NULL.

En resumen, sí, hay que considerar que nuestro código será leído por otras personas o por nosotros mismos en el futuro (o por un violento psicópata que sabe dónde vives), pero recuerda que, en Javascript, x == false no es funcionalmente equivalente a !x.

alwayslast.bash

Este script permite poner en un archivo las últimas N líneas de una salida. Por ejemplo, ping [...] | alwayslast.bash FILE 10 permitiría que FILE tuviera las últimas 10 líneas de la salida de ping.

This script allows for a file to have the las N lines of an output. For example, ping [...] | alwayslast.bash FILE 10 would make FILE always have the last 10 lines from the output of ping.

#!/bin/bash

if [ "$#" -ne 2 ]; then
 echo "Usage: $0 output_file maxlines"
 exit 2
fi

FILE="$1"
MAXLINES="$2"

START=0
LINES=1

while IFS= read -r NEWLINE; do
 BUFFER[START]="$NEWLINE"

 TEMPFILE=$(mktemp)
 trap -- 'rm "$TEMPFILE"' EXIT INT TERM

 for INDEX in $(seq "$((START+1))" "$((LINES-1))") $(seq 0 "$START"); do
 printf '%s\n' "${BUFFER[$INDEX]}"
 done > "$TEMPFILE"

 chmod --reference="$FILE" "$TEMPFILE"
 mv "$TEMPFILE" "$FILE"
 trap -- EXIT INT TERM

 LINES="$((LINES+1))"
 if [ "$LINES" -gt "$MAXLINES" ]; then
 LINES="$MAXLINES"
 fi
 START="$(((START + 1) % MAXLINES))"
done

Gracias a Eduardo Bustamante por sus correcciones.