Cómo aplicar set -o pipefail al fallar el primer comando en la tubería

Cómo aplicar set -o pipefail al fallar el primer comando en la tubería

Estoy intentando exportar datos de una base de datos de Postgres a un archivo en bash. Pero me gustaría asegurarme de que el archivo solo se sobrescriba si la conexión a la base de datos no falla (es decir, recupero algunos datos).

Probé el uso delfalla de tuberíaopción, sin embargo, si el primer comando falla con un error (el host no existe, por ejemplo), el comando cat aún se ejecuta y genera un archivo vacío (borrando el último contenido bueno que me gustaría evitar). En el siguiente ejemplo, myhost no es un host válido, por lo que el comando psql simplemente fallaría.

Entonces, la pregunta más importante es cómo asegurarse de que cuando se establece pipefail, los comandos posteriores no se ejecuten cuando falla el primer comando.

#!/bin/sh
set -o nounset
set -o errexit
set -o pipefail

PG_HOST=myhost

psql $PG_HOST -At -F$'\t' -c "SELECT * FROM mytable" | cat > /tmp/mytable.txt

Respuesta1

set -o pipefail -o errexitimpide que se ejecuten comandos posteriores, pero eso no le ayuda, porque no está intentando evitar unasubsecuentecomando de ser ejecutado. En una tubería producer | consumer, los comandos producery se ejecutanconsumeren paralelo. No puedes evitar consumerque comience si producerfalla porque, salvo un extraño accidente de sincronización, ya comenzó.

Si las dos únicas posibilidades son " consumertiene éxito y produce un resultado no vacío" y " consumerfalla y no produce ningún resultado", puede utilizarifnede moreutils de Joey Hess.

producer | ifne consumer

Sin embargo, no creo que eso funcione en su caso de uso: podría suceder que no haya filas coincidentes (falso negativo y obtendrá datos obsoletos), la conexión a la base de datos podría perderse en el medio (falso positivo y obtendrá datos truncados). ).

Si necesita saber si el productor tuvo éxito, debe esperar hasta que finalice antes de iniciar con el consumidor. Y dado que el consumidor aún no ha llegado, es necesario algo para almacenar el resultado.

Si la salida no contiene bytes nulos, termina en un solo carácter de nueva línea y no es demasiado grande, puede almacenarla en una variable de shell.

output=$(producer); producer_status=$?
if [ "$producer_status" -ne 0 ]; then
  echo >&2 "Producer failed with status $producer_status"
  exit "$producer_status"
fi
printf '%s\n' "$output" | consumer

En zsh y algunos otros shells, incluidos ksh93 y bash, esa última línea se puede simplificar a consumer <<<"$output".

Tenga en cuenta que la sustitución de comandos elimina las nuevas líneas finales. Si las líneas vacías finales son relevantes, una solución alternativa es cambiar la primera línea a

output=$(producer; ret=$?; echo .; exit "$?")
producer_status=$? output=${output%?}

$outputLuego contendrá el resultado completo, incluidos los caracteres de nueva línea finales, si los hay. Luego utilícelo printf %s "$output"en lugar de printf '%s\n' "$output"para alimentarlo al consumer.

Si la salida es potencialmente demasiado grande o puede contener bytes nulos, guárdela en un archivo temporal.

Respuesta2

Como dijo DopeGhoti,pipefail... simplemente significa que el error en cualquier punto de una cadena de tuberías se conservará para el código de salida[de un oleoducto].

Para hacer que el script se cierre por error, utilice set -e.

Para evitar la creación del archivo, cree uno temporal y cámbiele el nombre en caso de éxito, a saber:

set -e 
psql $PG_HOST -At -F$'\t' -c \
    "SELECT * FROM mytable"  >  /tmp/mytable.txt~
                          # ^^^ cf. Useless Use of Cat
mv /tmp/mytable.txt~ /tmp/mytable.txt

yo siempre usohacerpara este tipo de cosas, porque se detiene ante un error y me permite crear canalizaciones reiniciables.

información relacionada