Cómo aplicar set -o pipefail en el primer command que falla en la tubería

Estoy intentando exportar datos de una database de postgres a un file en bash. Pero me gustaría asegurarme de que el file solo se sobrescriba si la connection a la database no falla (es decir, obtengo algunos datos)

Intenté usar la opción pipefail ; sin embargo, si el primer command falla con un error (el host no existe, por ejemplo), el command cat aún se ejecuta y genera un file vacío (borrando el último contenido bueno del mismo que me gustaría evitar). En el ejemplo siguiente, myhost no es un host válido, por lo que el command psql simplemente fallará.

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

#!/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 

set -o pipefail errexit evita que se ejecuten commands posteriores, pero eso no lo ayuda, porque no está tratando de evitar que se ejecute un command posterior . En un producer | consumer tubería producer | consumer producer | consumer , los commands de producer y consumer ejecutan en paralelo . No se puede evitar que el consumer inicie si el producer falla porque, salvo un crash temporal impnetworkingecible, ya ha comenzado.

Si las dos únicas posibilidades son "el consumer tiene éxito y produce resultados no vacíos" y "el consumer falla y no produce ningún producto", puede usar ifne de las ifne de Joey Hess .

 producer | ifne consumer 

Aunque no creo que eso funcione en su caso de uso, puede que no haya filas coincidentes (falso negativo, y obtiene datos obsoletos), la connection de la database podría perderse en el medio (falso positivo, y obtendrá datos truncados) )

Si necesita saber si el productor tuvo éxito, entonces debe esperar hasta que haya terminado antes de iniciar el consumo. Y dado que el consumidor todavía no está presente, algo necesita almacenar la salida.

Si el resultado no contiene bytes nulos y no es demasiado grande, puede almacenarlo 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 ksh93, bash o zsh, la última línea se puede simplificar a consumer <<<"$output" .

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

 output=$(producer; echo a); producer_status=$?; output=${output%?} 

Si el resultado es potencialmente demasiado grande o puede contener bytes nulos, guárdelo en un file temporal.

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 una tubería].

Para hacer que el script salga por error, use set -e .

Para evitar la creación del file, cree uno temporal y renómbrelo en caso de éxito, por ejemplo:

 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 

Siempre utilizo make para este tipo de cosas, porque se detiene por error y me permite build tuberías reiniciables.