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


61

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

现在,我正在使用time -f '%e' ...(或者command time -f '%e' ...由于内置的​​Bash而已),但是由于我已经重定向了已执行命令的输出,因此我对如何捕获time命令的输出一无所知。基本上这里的问题是到输出分离time从执行的命令(或多个)的输出。

我想要的功能是计算从开始命令到完成之间以秒为单位(整数)的时间。它不一定是time命令或相应的内置命令。


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

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

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


最直接的方法来获取数据,处理它,而仍然让它正常运行是用做在C程序fork()execvp()wait3()/wait4()。最终,这就是时间和朋友在做什么。我不知道在不重定向到文件或类似方法的情况下在bash / perl中执行类似操作的方法。
penguin359 2011年

还有就是你可能会发现有趣的一个相关的问题会在这里
Caleb

@Caleb:谢谢。确实很有趣。但是,出于我的目的,只需在脚本中完成就可以了。
0xC0000022L

Answers:


85

要获得timevar 的输出,请使用以下命令:

usr@srv $ mytime="$(time ( ls ) 2>&1 1>/dev/null )"
usr@srv $ echo "$mytime"

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

您也可以只要求一个时间类型,例如utime:

usr@srv $ utime="$( TIMEFORMAT='%lU';time ( ls ) 2>&1 1>/dev/null )"
usr@srv $ echo "$utime"
0m0.000s

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

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

4
我不想抛弃命令的输出。因此,我猜您的第三个代码块最接近我的想法。尽管我会DIFF=$((END-START))使用算术表达式将最后一个写为,:) ... 感谢您的回答。+1
0xC0000022L

1
@STATUS_ACCESS_DENIED:Bash不执行浮点算术,因此,如果您想要的分辨率优于第二种分辨率(.%N在binfalse的代码中),则需要进行更精确的bc计算。
Gilles

@吉尔斯:我知道。正如我在问题中所写的那样,整数很好。无需拥有比秒更高的分辨率。但是,谢谢,日期调用将不得不更改。我已经意识到了。
0xC0000022L

6
仅供参考,日期格式%N似乎在Mac OS X上不起作用,它仅返回“ N”。在Ubuntu上还可以。
大卫

请注意,虽然time (cmd) 2> something将时序输出重定向到的工作file,但这并不意味着(根据文档),也没有在其他shell中time作为关键字,可以将其视为bug。我不会依赖它,因为它可能在的未来版本中不起作用bash
斯特凡Chazelas

17

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

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

这将写入outfd 1,errfd 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:/')

err在fd 2上拥有时间,在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之前和之后运行是一个简单的解决方案(如果由于加载外部命令花费了额外的时间而更加不精确)。


哇,要测试很多。非常感谢您的广泛答复。+1。
0xC0000022L

7

随着时间的推移,命令输出在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; )

执行此操作时,命令的输出将为in outputfile,运行时间为in $FOO


1
哦,我没有意识到。我知道time写入stderr的内容,但是我没有意识到它将把stdout和已执行命令的stderr合并到其stdout中。但是通常没有直接方法(即没有中间文件)吗?+1。
0xC0000022L

3
@STATUS_ACCESS_DENIED:time不合并其命令stdoutstderr。我展示的方式假设您只需要命令的stdout输出。由于bash将仅存储来自的内容stdout,因此您必须重定向。如果可以放心地删除命令的stderr,则时间将始终在stderr的最后一行。如果确实需要两个命令的输出流,建议将其包装在另一个脚本中。
marinus

6

如果您处于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.

真好 我之前从未听说过SECONDS
Bruno9779,18年

您是否偶然知道从哪个Bash版本开始?
0xC0000022L

4

为此,最好使用times(比time)更好,bash因为它可以打印外壳程序以及从外壳程序运行的进程的累计用户和系统时间,示例使用:

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

请参阅:help -m times有关更多信息。


4

在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"
}

1

安装/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"

/usr/bin/time在许多系统上
Basile Starynkevitch

如果您仔细观察,我会在我的问题中指出。time是Bash内置的Shell(可能还有其他Shell),但是只要time可执行文件的路径在PATH,我们就可以command time用来确保我们运行的是外部命令而不是内置命令。
0xC0000022L

1

正如在处理以上声明时要特别注意的那样。我很惊讶地在我的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
time也是一个内置的外壳。
巴西尔·斯塔林凯维奇

如果要确保正在运行命令,请使用command,如果要确保正在运行内置命令,请使用builtin。这里没有魔术。使用help找出可用的命令或type <command>要弄清楚什么类型<command>的(如内置,外部命令,函数或别名)。
0xC0000022L

Basile,您说得很对。我没有发现command命令的重要性。事实确保可以调用/ usr / bin / time。这意味着以下两个语句是兼容的:$ command time -f %e sleep 4$ /usr/bin/time -f %e sleep
Paul Pritchard

0

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

它也不会分叉subshel​​l或践踏除真实用户sys之外的任何变量,并且不会以其他方式干扰脚本的运行

timer () {
  { time { "$@" ; } 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 -- $? "$@"
  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

如果最终被递归调用,可能会很有用,以避免按时践踏;但随后应将rus等声明为本地使用。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.