如何包装命令来测量其经过的时间?
目前我使用eval
:
do_cmd_named()
{
local name=$1
local cmd=$2
echo "$name"
local start_time=$(date +%s)
eval "$cmd 2>&1"
local exit_status=$?
local end_time=$(date +%s)
local elapsed_time_sec=$((end_time-start_time))
local elapsed_time_min_sec=$(date -ud "@$elapsed_time_sec" +'%M:%S')
if [[ $exit_status -ne 0 ]]
then
echo "$name failed with exit status $exit_status (elapsed time $elapsed_time_min_sec)"
return $exit_status
else
echo "$name done (elapsed time $elapsed_time_min_sec)"
fi
}
job()
{
sleep 1
}
do_cmd_named "do job" "job"
这导致:
do job
do job done (elapsed time 00:01)
对于我的情况,这种方法几乎作品。然而,这种方法被认为是不好的,因为它违反了一些规则Bash常见问题解答。例如,“不要将代码放入变量中”来自Bash 常见问题解答#50(也可以看看Bash常见问题解答 #48)。
那么,问题是:如何正确地做到这一点?
答案1
- 将命令(和参数)捕获到大批然后你就可以避免
eval
- 使用 bash 内置变量
$SECONDS
do_cmd_named() {
local name=$1
shift
local -a cmd=("$@")
echo "$name"
SECONDS=0
"${cmd[@]}"
local status=$?
local elapsed_time_sec=$SECONDS
# print messages ...
}
do_cmd_named "job name" job arg1 arg2
$SECONDS 的问题是,在嵌套调用的情况下,$SECONDS 会重置为 0。
是的,这是真的。
另一种方法是在函数开始时捕获 SECONDS 的当前值并在最后执行一些算术运算。
一个例子:
rectest() {
local n=$1 delay=$2 start=$SECONDS
((n == 3)) && return
sleep $delay
rectest $((n + 1)) $((delay + 3))
echo "at level $n, delay is $delay and duration is $((SECONDS - start))"
}
运行它,所有输出都会在 15 秒后出现:
$ rectest 0 2
at level 2, delay is 8 and duration is 8
at level 1, delay is 5 and duration is 13
at level 0, delay is 2 and duration is 15
答案2
如果可以选择切换到 zsh:
$ TIMEFMT='%J done (elapsed time: %E)'
$ time sleep 1
sleep 1 done (elapsed time: 1.00s)