Como impor set -o pipefail ao falhar primeiro comando no pipe

1

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 (por exemplo, eu recebo alguns dados de volta)

Tentei usar a opção pipefail , no entanto, se o primeiro comando falhar com um erro (o host não existe por exemplo), o comando cat ainda é executado e gera um arquivo vazio (apagando o último bom conteúdo dele que eu gostaria de evitar). No exemplo abaixo, myhost é um host inválido, então o comando psql simplesmente falharia.

Portanto, a questão mais importante é como ter certeza de que quando o pipefail é definido, os comandos subseqüentes não são executados quando o primeiro comando falha.

#!/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
    
por BraveHeart 16.02.2016 / 21:12

2 respostas

2

set -o pipefail errexit impede que os comandos subseqüentes sejam executados, mas isso não ajuda, porque você não está tentando impedir que um comando subseqüente seja executado. Em um pipeline producer | consumer , os comandos producer e consumer executam em paralelo . Você não pode impedir que o consumer inicie se producer falhar porque, salvo um acidente cronometrado, já começou.

Se as duas únicas possibilidades forem " consumer for bem-sucedida e produzir saída não vazia" e " consumer falhar e não produzir saída", use ifne dos moreutils de Joey Hess .

producer | ifne consumer

No entanto, não acho que funcione no seu caso de uso - pode não haver linhas correspondentes (falso negativo e você obtém 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 foi bem-sucedido, é necessário aguardar até que seja concluído antes de iniciar o consumidor. E como o consumidor ainda não está por perto, algo precisa armazenar a saída.

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

No ksh93, bash ou zsh, essa última linha pode ser simplificada para consumer <<<"$output" .

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

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

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

    
por 17.02.2016 / 01:28
0

Como o DopeGhoti disse, 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 pipeline].

Para fazer com que o script saia por erro, use set -e .

Para evitar a criação do arquivo, crie um arquivo temporário e renomeie-o como 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 uso make para esse tipo de coisa, porque ele pára em erro e me permite construir pipelines reinicializáveis.

    
por 17.02.2016 / 01:53

Tags