Как принудительно применить set -o pipefail при неудачном выполнении первой команды в канале

Как принудительно применить set -o pipefail при неудачном выполнении первой команды в канале

Я пытаюсь экспортировать данные из базы данных postgres в файл в bash. Но я хотел бы убедиться, что файл перезаписывается только в том случае, если соединение с базой данных не прерывается (т.е. я получаю некоторые данные обратно)

Попробовал использоватьтрубапровалoption, однако, если первая команда завершается ошибкой (например, хост не существует), команда cat все равно выполняется и генерирует пустой файл (стирая из него последнее хорошее содержимое, чего я хотел бы избежать). В примере ниже myhost — недопустимый хост, поэтому команда psql просто завершится ошибкой.

Поэтому более важный вопрос заключается в том, как гарантировать, что при установке pipefail последующие команды не будут выполняться в случае сбоя первой команды.

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

решение1

set -o pipefail -o errexitпредотвращает выполнение последующих команд, но это вам не поможет, потому что вы не пытаетесь предотвратитьпоследующийкоманда от выполнения. В конвейере producer | consumerкоманды producerи consumerвыполняютсяв параллели. Вы не можете предотвратить consumerзапуск в случае producerсбоя, поскольку, за исключением непредвиденной ошибки синхронизации, он уже запущен.

Если единственными двумя возможностями являются « consumerуспешно и создает непустой вывод» и « consumerнеуспешно и не создает никакого вывода», вы можете использоватьifneиз moreutils Джоуи Хесса.

producer | ifne consumer

Однако я не думаю, что это сработает в вашем случае — может так случиться, что не будет ни одной совпадающей строки (ложноотрицательный результат, и вы получите устаревшие данные), соединение с базой данных может быть потеряно в середине (ложноположительный результат, и вы получите усеченные данные).

Если вам нужно узнать, успешно ли выполнился производитель, то вам нужно дождаться его завершения, прежде чем запускать потребителя. А поскольку потребителя еще нет, что-то должно хранить вывод.

Если вывод не содержит нулевых байтов, заканчивается одним и только одним символом новой строки и не слишком велик, вы можете сохранить его в переменной оболочки.

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

В zsh и некоторых других оболочках, включая ksh93 и bash, последнюю строку можно упростить до consumer <<<"$output".

Обратите внимание, что подстановка команд удаляет завершающие пустые строки. Если завершающие пустые строки имеют значение, обходной путь — изменить первую строку на

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

$outputбудет затем содержать полный вывод, включая конечные символы новой строки, если таковые имеются. Затем используйте printf %s "$output"вместо , printf '%s\n' "$output"чтобы передать его в consumer.

Если вывод потенциально слишком большой или может содержать нулевые байты, сохраните его во временном файле.

решение2

Как сказал DopeGhoti,pipefail... просто означает, что ошибка в любой точке цепочки каналов будет сохранена для кода выхода[трубопровода].

Чтобы завершить работу скрипта в случае ошибки, используйте set -e.

Чтобы предотвратить создание файла, создайте временный файл и переименуйте его в случае успеха, а именно:

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

Я всегда используюделатьдля такого рода вещей, потому что он останавливается при возникновении ошибки и позволяет мне создавать перезапускаемые конвейеры.

Связанный контент