Я пытаюсь экспортировать данные из базы данных 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
Я всегда используюделатьдля такого рода вещей, потому что он останавливается при возникновении ошибки и позволяет мне создавать перезапускаемые конвейеры.