Como impor set -o pipefail na falha do primeiro comando no pipe

Como impor set -o pipefail na falha do primeiro comando no pipe

Estou tentando exportar dados de um banco de dados postgres para um arquivo no bash. Mas eu gostaria de ter certeza de que o arquivo só será sobrescrito se a conexão com o banco de dados não falhar (ou seja, eu receber alguns dados de volta)

Tentei usar ofalha no tuboopção, no entanto, se o primeiro comando falhar com um erro (host não existe, por exemplo), o comando cat ainda será executado e gerará um arquivo vazio (eliminando o último conteúdo válido que eu gostaria de evitar). No exemplo abaixo, myhost é um host inválido, então o comando psql simplesmente falharia.

Portanto, a questão maior é como garantir que, quando o pipefail estiver definido, os comandos subsequentes não sejam executados quando o primeiro comando falhar.

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

Responder1

set -o pipefail -o errexitimpede que comandos subsequentes sejam executados, mas isso não ajuda, porque você não está tentando impedir umsubseqüentecomando seja executado. Em um pipeline producer | consumer, os comandos producere são executadosconsumerem paralelo. Você não pode impedir consumera partida se producerfalhar porque, salvo um acidente estranho de cronometragem, ela já começou.

Se as duas únicas possibilidades forem “ consumersucesso e produção de saída não vazia” e “ consumerfalha e não produção de saída”, você pode usarifnede moreutils de Joey Hess.

producer | ifne consumer

Porém, não acho que isso funcione no seu caso de uso - pode acontecer de não haver linhas correspondentes (falso negativo e você obter dados obsoletos), a conexão com o banco de dados pode ser perdida no meio (falso positivo e você obter dados truncados ).

Se você precisa saber se o produtor teve sucesso, então você precisa esperar até terminar antes de iniciar o consumidor. E como o consumidor ainda não existe, algo precisa armazenar a produção.

Se a saída não contiver bytes nulos, terminar em um e apenas um caractere de nova linha e não for muito grande, você poderá armazená-la em uma variável 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

No zsh e em alguns outros shells, incluindo ksh93 e bash, a última linha pode ser simplificada para consumer <<<"$output".

Observe que a substituição do comando tira as novas linhas finais. Se as linhas vazias finais forem relevantes, uma solução alternativa é alterar a primeira linha para

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

$outputconterá então a saída completa, incluindo os caracteres de nova linha à direita, se houver. Em seguida, use printf %s "$output"em vez de printf '%s\n' "$output"para alimentá-lo ao arquivo consumer.

Se a saída for potencialmente muito grande ou puder conter bytes nulos, armazene-a em um arquivo temporário.

Responder2

Como disse DopeGhoti,pipefail... significa apenas que o erro em qualquer ponto de uma cadeia de tubos será preservado para o código de saída[de um gasoduto].

Para fazer com que o script seja encerrado em caso de erro, use set -e.

Para evitar a criação do arquivo, crie um temporário e renomeie-o com sucesso, 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

Eu sempre usofazerpara esse tipo de coisa, porque ele para em caso de erro e me permite criar pipelines reinicializáveis.

informação relacionada