我想获取命令输出的字节数和 sha1sum。
原则上,人们总是可以做这样的事情:
BYTES="$( somecommand | wc -c )"
DIGEST="$( somecommand | sha1sum | sed 's/ .*//' )"
...但是,对于我感兴趣的用例,somecommand
相当耗时,并且会产生大量输出,所以我宁愿只调用它一次。
我想到的一种方法是这样的
evil() {
{
somecommand | \
tee >( wc -c | sed 's/^/BYTES=/' ) | \
sha1sum | \
sed 's/ .*//; s/^/DIGEST=/'
} 2>&1
}
eval "$( evil )"
...这似乎有效,但让我内心有点死了。
我想知道是否有更好(更稳健、更通用)的方法来将管道不同段的输出捕获到单独的变量中。
编辑:我目前正在解决的问题是bash
,所以我最感兴趣的是这个 shell 的解决方案,但我也做了很多zsh
编程,所以我对这些解决方案也有一些兴趣。
EDIT2:我尝试将 Stéphane Chazelas 的解决方案移植到bash
,但它不太有效:
#!/bin/bash
cmd() {
printf -- '%1000s'
}
bytes_and_checksum() {
local IFS
cmd | tee >(sha1sum > $1) | wc -c | read bytes || return
read checksum rest_ignored < $1 || return
}
set -o pipefail
unset bytes checksum
bytes_and_checksum "$(mktemp)"
printf -- 'bytes=%s\n' $bytes
printf -- 'checksum=%s\n' $checksum
当我运行上面的脚本时,我得到的输出是
bytes=
checksum=96d89030c1473585f16ec7a52050b410e44dd332
的值checksum
是正确的。bytes
我不明白为什么没有设置的值。
EDIT3:好的,感谢@muru 的提示,我解决了问题:
#!/bin/bash
cmd() {
printf -- '%1000s'
}
bytes_and_checksum() {
local IFS
read bytes < <( cmd | tee >(sha1sum > $1) | wc -c ) || return
read checksum rest_ignored < $1 || return
}
set -o pipefail
unset bytes checksum
bytes_and_checksum "$(mktemp)"
printf -- 'bytes=%s\n' $bytes
printf -- 'checksum=%s\n' $checksum
现在:
bytes=1000
checksum=96d89030c1473585f16ec7a52050b410e44dd332
不幸的是...
...当产生的输出比上面的玩具示例中的情况多得多时,我的bytes_and_checksum
函数就会停止(死锁?) 。cmd
回到绘图板...
使用临时文件会更容易。在
zsh
:请注意,许多
wc
实现在其输出的数字周围包含空格。read
默认值是$IFS
剥离它们。请注意, 的退出状态
sha1sum
会丢失。创建临时文件时
=()
根本不输出任何内容。当提供给该临时文件的命令(此处为匿名函数)返回时,该临时文件将自动删除。在
cmd > file | other-cmd
,cmd
的输出tee
内部由 d 进行处理,zsh
因为它被重定向了两次,所以这里都是 tosha1sum
和 towc
。我们cmd
进行包装{...}
以确保 zsh 等待进程重定向完成。sha1sum
这里作为两者的输出,wc
保证不大于几个字节,它们也可以发送到管道,并且您不必同时从这些管道中读取(zsh 可以做到这一点,因为它有一个 / 的接口select()
,poll()
但是不是bash)。这可以按顺序完成,而不会导致死锁,因此它是tee 到不同变量的简单版本。在基于 Linux 的系统上(当管道的 fd 的行为类似于命名管道时)
/dev/fd/x
:x
(甚至可以在 bash 中工作)。
有关较大输出时遇到的死锁的详细信息,另请参阅tee + cat:多次使用输出,然后连接结果。
我正在使用一个备份bash脚本,该脚本具有以下帮助程序“中间”函数,这些函数将“假定的文件名”作为参数(请参见下面的 tar.gz 示例):
例子:
我在一个非常耗时、CPU 和 IO 消耗的脚本中使用它,如下所示:
因此,我根据随机内存/磁盘位翻转检查了我的备份。它创建“mybackup.tar.sha1”文件,就像实际创建“mybackup.tar”并进行校验和一样,而实际上在此示例中,未压缩的数据从未写入磁盘上。
警告:
pipechecksum
即使出现错误,也不会终止脚本set -euo pipefail
。pipechecksum
在校验和不匹配时返回非零的替代方案:看起来不错,但我今天才带来它,还不能认为它已得到证实。