set -eu
VAR=$(zcat file.gz  |  head -n 12)

工作正常
set -eu   -o pipefail
VAR=$(zcat file.gz  |  head -n 12)

导致故障以失败退出。
这是如何导致管道故障的?
注意,file.gz包含数百万行(约750MB,压缩)。

最佳答案

想一想,想一想。
您告诉shell,如果任何组件发生故障,则应将整个管道视为已失败。
您告诉zcat将其输出写入head
然后你告诉head退出12行后,从一个更长的-12行输入流。
当然,您有一个错误:zcat的目标管道已提前关闭,无法成功编写输入文件的解压缩版本!它无法知道这是由于用户的意图,通过一些错误的发生。
如果您使用zcat写入磁盘,并且它用完了空间,或者到网络流并有连接丢失,则它将完全正确且适合于以指示失败的状态退出。这只是这条规则的另一个例子。
操作系统给出的zcat特定错误是EPIPE,由write系统调用在以下条件下返回:试图写入未打开供任何进程读取的管道。
head(此FIFO的唯一读出器)已经退出之后,对于任何不返回Enpe的管道的输入端的写入都将是一个错误。对于zcat默默地忽略编写其输出的错误,从而能够生成不具有反映此事件的退出状态的不准确的输出流,同样也是一个错误。
如果不想更改任何shell选项,顺便说一句,可以考虑使用进程替换:

var=$(head -n 12 < <(zcat file.gz))

在这种情况下,zcat不是管道组件,为了确定成功,不考虑其退出状态。(如果您想得出独立的成功/失败判断,您可以测试$var是否有12行)。
一个更全面的解决方案可以通过引入一个Python解释器来实现,它支持本地gzip。嵌入在shell脚本中的本机Python实现(与Python 2和3.x兼容)可能类似于:
zhead_py=$(cat <<'EOF'
import sys, gzip
gzf = gzip.GzipFile(sys.argv[1], 'rb')
outFile = sys.stdout.buffer if hasattr(sys.stdout, 'buffer') else sys.stdout
numLines = 0
maxLines = int(sys.argv[2])
for line in gzf:
    if numLines >= maxLines:
        sys.exit(0)
    outFile.write(line)
    numLines += 1
EOF
)
zhead() { python -c "$zhead_py" "$@"; }

……如果你的输入数据用完了,它不会失败,但它会通过一个失败的退出状态来进行真正的I/O故障或其他意外事件。(用法为zhead,从zhead in.gz 5中读取5行)。

10-06 03:37