我想要一个脚本,在标准输入的每一行前面加上生成它需要多长时间的信息。基本上用于输入:
foo
bar
baz
我想拥有
0 foo
10 bar
5 baz
其中10是打印foo和打印bar之间经过10秒,与5类似,打印bar之后需要5秒才能打印baz。
我知道有一个实用程序ts
可以显示时间戳并且我知道https://github.com/paypal/gnomon,但我不想使用 javascript 来做到这一点。
是否有一个标准工具,或者我应该使用 awk 进行处理?
答案1
我们假设生成输出的脚本称为generate
。然后,显示生成每一行所需的秒数:
$ generate | ( t0=$(date +%s); while read -r line; do t1=$(date +%s); echo " $((t1-t0)) $line"; t0=$t1; done )
2 foo
0 foo
5 foo
3 foo
分布在多行上的相同命令如下所示:
generate | ( t0=$(date +%s)
while read -r line
do
t1=$(date +%s)
echo " $((t1-t0)) $line"
t0=$t1
done
)
或者,为了方便起见,我们可以定义一个包含以下代码的 shell 函数:
timer() { t0=$(date +%s); while read -r line; do t1=$(date +%s); echo " $((t1-t0)) $line"; t0=$t1; done; }
我们可以如下使用这个函数:
$ generate | timer
0 foo
2 foo
4 foo
3 foo
怎么运行的
t0=$(date +%s)
这会捕获脚本开始时的当前时间(以秒为单位)。
while read -r line; do
这将启动一个从标准输入读取的循环
t1=$(date +%s)
这捕获了自捕获当前行以来的秒数时间。
echo " $((t1-t0)) $line"
这会打印出当前行所花费的时间(以秒为单位)。
t0=$t1
这将更新
t0
下一行。done
这标志着循环的结束
while
。
答案2
trickle () {
awk 'BEGIN { t = systime(); OFS="\t" }
{ print systime() - t, $0 }
{ t = systime() }'
}
这个小 shell 函数只是包装了一个awk
脚本(与 GNU 和 兼容awk
,但由于缺少而mawk -Wi
与 BSD 不兼容)。它将输出每行输入,并以自最后一行输出以来的整秒数为前缀。第一行将以零为前缀。awk
systime()
测试:
$ { echo hello; sleep 2; echo world } | trickle
0 hello
2 world
该awk
脚本显然也可以独立运行:
$ some_command | awk 'BEGIN{OFS="\t";t=systime()}{print systime()-t,$0;t=systime()}
例如
$ { echo hello; sleep 2; echo world; } | awk 'BEGIN{OFS="\t";t=systime()}{print systime()-t,$0;t=systime()}'
0 hello
2 world
答案3
出色地,更多实用程序 ts
似乎是一个完美的工具:
自开始以来的几秒钟:
$ seq 5 | pv -qL 1 | ts -s %s
2 1
4 2
6 3
8 4
10 5
或者更精确地说:
$ seq 5 | pv -qL 1 | ts -s %.s
1.949228 1
3.933483 2
5.917923 3
7.902231 4
9.886618 5
或者使用zsh
or ksh93
($SECONDS
默认情况下是整数,但您可以使用 ; 使其浮动typeset -F
,pdksh
也可以bash
使用$SECONDS
,但仅限整数):
$ typeset -F SECONDS=0; seq 5 | pv -qL 1 | while IFS= read -r line; do
printf '%.3f %s\n' "$SECONDS" "$line"; done
1.987 1
3.972 2
5.956 3
7.941 4
9.925 5
行与行之间的秒数
$ seq 5 | pv -qL 1 | ts -i %.s
1.947814 1
1.984442 2
1.984456 3
1.984151 4
1.984195 5
zsh
/ ksh93
:
$ typeset -F SECONDS; seq 5 | pv -qL 1 | while SECONDS=0; IFS= read -r line; do
printf '%.6f %s\n' "$SECONDS" "$line"; done
1.985298 1
1.984322 2
1.984212 3
1.984240 4
1.984441 5
答案4
首先,利用 bash 内置函数创建一个简单的函数SECONDS
。请注意,我们在每次调用时将其初始化为零。
_time() { SECONDS=0; out="$($@)"; echo "[${SECONDS}] ${out:-no output}"; }
接下来,使用此_time
函数从命令列表中运行命令,命令列表可以是命令的文件,或者其他任何内容。
while read cmd; do _time $cmd; done < <(echo -e "sleep 5\nsleep 2\nsleep 1")
在这里, sleep 没有输出,但你的命令可能会有。这只是为了展示 SECONDS 的使用,因为它与这个问题有关。此示例将输出以下默认输出no output
:
[5] no output
[2] no output
[1] no output