it-swarm.cn

如何测量程序执行时间并将其存储在变量中

为了找出Bash(v4 +)脚本中的某些操作需要花费多长时间,我想“单独”解析time命令的输出,并(最终)将其捕获到Bash变量(let VARNAME=...)。

现在,我正在使用time -f '%e' ... (更确切地说 command time -f '%e' ...(因为内置了Bash),但是由于我已经重定向了已执行命令的输出,因此我对如何捕获time命令的输出一无所知。基本上,这里的问题是time用于分离输出与已执行命令的输出。

我想要的是功能计算开始执行命令和完成命令之间的时间(以秒为单位)。不必是time命令或相应的内置命令。


编辑:给出以下两个有用的答案,我想添加两个说明。

  1. 我不想抛弃已执行命令的输出,但是最终以stdout还是stderr结束并不重要。
  2. 我更喜欢直接方法而不是间接方法(即直接捕获输出而不是将其存储在中间文件中)。

到目前为止,使用date的解决方案接近我想要的解决方案。

65
0xC0000022L

要将time的输出转换成var,请使用以下命令:

[email protected] $ mytime="$(time ( ls ) 2>&1 1>/dev/null )"
[email protected] $ echo "$mytime"

real    0m0.006s
user    0m0.001s
sys     0m0.005s

您也可以只要求输入一种时间类型,例如时间:

[email protected] $ utime="$( TIMEFORMAT='%lU';time ( ls ) 2>&1 1>/dev/null )"
[email protected] $ echo "$utime"
0m0.000s

要获得时间,您还可以使用date +%s.%N,因此请在执行之前和之后使用它并计算差异:

START=$(date +%s.%N)
command
END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)
# echo $DIFF
89
binfalse

在bash中,time构造的输出转到其标准错误,您可以重定向它影响的管道的标准错误。因此,让我们从写入其输出和错误流的命令开始:sh -c 'echo out; echo 1>&2 err'。为了不使命令的错误流与time的输出混淆,我们可以暂时将命令的错误流转移到另一个文件描述符:

{ time -p sh -c 'echo out; echo 1>&2 err' 2>&3; }

这会将out写入fd 1,将err写入fd 3,并将时间写入fd 2:

{ time -p sh -c 'echo out; echo 1>&2 err' 2>&3; } \
    3> >(sed 's/^/ERR:/') 2> >(sed 's/^/TIME:/') > >(sed 's/^/OUT:/')

在fd 2上有err并在fd 3上有时间会更令人愉快,因此我们交换它们,这很麻烦,因为没有直接的方法可以交换两个文件描述符:

{ { { time -p sh -c 'echo out; echo 1>&2 err' 2>&3; } 3>&2 2>&4; } 4>&3; } 3> >(sed 's/^/TIME:/') 2> >(sed 's/^/ERR:/') > >(sed 's/^/OUT:/')

这显示了如何对命令的输出进行后处理,但是,如果要同时捕获命令的输出及其时间,则需要更加努力。一种解决方案是使用临时文件。实际上,如果您需要捕获命令的标准错误及其标准输出,则这是唯一可靠的解决方案。但是,否则,您可以捕获整个输出并利用time具有可预测的格式这一事实(如果使用time -p以获得 POSIX格式 或bash特定的TIMEFORMAT变量)。

nl=$'\n'
output=$(TIMEFORMAT='%R %U %S %P'; mycommand)
set ${output##*$nl}; real_time=$1 user_time=$2 system_time=$3 cpu_percent=$4
output=${output%$nl*}

如果您只关心挂钟时间,则在之前和之后运行date是一个简单的解决方案(如果由于加载外部命令花费了额外的时间而更加不精确)。

17

如果您使用的是bash(而不是sh),并且不需要亚秒精度,则可以完全跳过对date的调用,而不会产生任何额外的开销进程,而不必分离合并的输出,也不必捕获和解析任何命令的输出:

# SECONDS is a bash special variable that returns the seconds since set.
SECONDS=0
mycmd <infile >outfile 2>errfile
DURATION_IN_SECONDS=$SECONDS
# Now `$DURATION_IN_SECONDS` is the number of seconds.
8
sanpath

随着时间的推移,命令输出在stdout上输出,时间在stderr上输出。因此,要分离它们,您可以执行以下操作:

command time -f '%e' [command] 1>[command output file] 2>[time output file]

但是,现在时间已成定案。我认为Bash不能直接将stderr放入变量中。如果您不介意将命令的输出重定向到某个地方,则可以执行以下操作:

FOO=$((( command time -f '%e' [command]; ) 1>outputfile; ) 2>&1; )

执行此操作时,命令的输出将在outputfile中,而运行时间将在$FOO

7
marinus

在OSX中,将所有先前的响应放在一起

ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G31+

你可以喜欢

microtime() {
    python -c 'import time; print time.time()'
}
compute() {
    local START=$(microtime)
    #$1 is command $2 are args
    local END=$(microtime)
    DIFF=$(echo "$END - $START" | bc)
    echo "$1\t$2\t$DIFF"
}
4
loretoparisi

为此,最好在times中使用time(比bash)更好,这会打印命令行管理程序以及从命令行管理程序运行的进程的累积用户和系统时间。采用:

$ (sleep 2; times) | (read tuser tsys; echo $tuser:$tsys)
0m0.001s:0m0.003s

参见:help -m times了解更多信息。

4
kenorb

安装/bin/time(例如pacman -S time

因此,尝试-f标志时不会出现错误:

$ time -f %e sleep 0.5
bash: -f: command not found

real    0m0.001s
user    0m0.001s
sys     0m0.001s

您可以实际使用它:

$ /bin/time -f %e sleep 0.5
0.50

并获得所需的内容-变量中的时间(示例使用%e表示实际经过时间,对于其他选项,请检查man time):

#!/bin/bash
tmpf="$(mktemp)"
/bin/time -f %e -o "$tmpf" sleep 0.5
variable="$(cat "$tmpf")"
rm "$tmpf"

echo "$variable"
1
Grzegorz Wierzowiecki

正如在处理以上声明时要特别注意的那样。我很惊讶地在我的Ubuntu 16.04 Xenial系统上看到这两个结果:

$ which time
/usr/bin/time

$ time -f %e sleep 4
-f: command not found
real    0m0.071s
user    0m0.056s
sys     0m0.012s

$ /usr/bin/time -f %e sleep 4
4.00

我没有设置任何别名,所以我不知道为什么会这样。

1
Paul Pritchard

尝试此操作,它将运行一个带有参数的简单命令,并将时间$ real $ user $ sys放置并保留退出代码。

它也不会派生子shell或践踏除实际用户sys之外的任何变量,并且不会以其他方式干扰脚本的运行

timer () {
  { time { "[email protected]" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $?
  read -d "" _ real _ user _ sys _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1
}

例如.

  timer find /bin /sbin /usr rm /tmp/
  echo $real $user $sys

注意:它仅使用简单命令而不使用管道,管道的组件在子shell中运行

此版本允许您将应接收3次的变量的名称指定为$ 1:

timer () {
  { time { "${@:4}" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $? "[email protected]"
  read -d "" _ "$2" _ "$3" _ "$4" _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1
}

例如.

  timer r u s find /bin /sbin /usr rm /tmp/
  echo $r $u $s

如果最终被递归调用,可能会很有用,以避免按时践踏;但是r u s etc应该在使用中声明为本地。

http://blog.sam.liddicott.com/2016/01/timeing-bash-commands.html

0
Sam Liddicott