如何在管道中第一个命令失败时强制执行 set -o pipelinefail

如何在管道中第一个命令失败时强制执行 set -o pipelinefail

我正在尝试将数据从 postgres 数据库导出到 bash 中的文件。但我想确保只有在与数据库的连接没有失败的情况下才会覆盖该文件(即我取回一些数据)

尝试使用管道故障选项,但是如果第一个命令因错误而失败(例如主机不存在),则 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,执行producerconsumer命令在平行下。如果失败,你无法阻止consumer启动,producer因为除非出现异常的计时事故,否则它已经开始了。

如果唯一的两种可能性是“consumer成功并产生非空输出”和“consumer失败且不产生输出”,您可以使用ifne来自 Joey Hess 的 moreutils

producer | ifne consumer

我认为这在你的用例中不起作用 - 可能碰巧没有匹配的行(误报,你得到过时的数据),数据库连接可能在中间丢失(误报,你得到被截断的数据) )。

如果您需要知道生产者是否成功,那么您需要等到它完成后再启动消费者。由于消费者尚未出现,因此需要存储输出。

如果输出不包含空字节、以一个且仅一个换行符结尾并且不是太大,则可以将其存储在 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

在 zsh 和其他一些 shell(包括 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

我总是用制作对于这类事情,因为它会在错误时停止并让我构建可重新启动的管道。

相关内容