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行)。