如何计算时间消耗在bash脚本?

我使用date +"%T"打印开始和结束时间,结果如下:

10:33:56
10:36:10

我如何计算并打印这两者之间的差值呢?

我想要的是:

2m 14s
376106 次浏览

Bash有一个方便的SECONDS内置变量,它跟踪自shell启动以来已经过的秒数。此变量在赋值时保留其属性,赋值后返回的值为自赋值后的秒数加上赋值。

因此,你可以在启动计时事件之前将SECONDS设置为0,在事件之后读取SECONDS,并在显示之前进行时间算术。

#!/usr/bin/env bash


SECONDS=0
# do some work
duration=$SECONDS
echo "$(($duration / 60)) minutes and $(($duration % 60)) seconds elapsed."

由于这个解决方案不依赖于date +%s(这是一个GNU扩展),所以它可以移植到Bash支持的所有系统。

以下是我的做法:

START=$(date +%s);
sleep 1; # Your stuff
END=$(date +%s);
echo $((END-START)) | awk '{print int($1/60)":"int($1%60)}'

非常简单,取开始时的秒数,然后取结束时的秒数,打印出以分钟为单位的差值:秒。

我想提出另一种避免调用date命令的方法。如果你已经收集了%T日期格式的时间戳,这可能会有帮助:

ts_get_sec()
{
read -r h m s <<< $(echo $1 | tr ':' ' ' )
echo $(((h*60*60)+(m*60)+s))
}


start_ts=10:33:56
stop_ts=10:36:10


START=$(ts_get_sec $start_ts)
STOP=$(ts_get_sec $stop_ts)
DIFF=$((STOP-START))


echo "$((DIFF/60))m $((DIFF%60))s"

我们甚至可以用同样的方法处理毫秒。

ts_get_msec()
{
read -r h m s ms <<< $(echo $1 | tr '.:' ' ' )
echo $(((h*60*60*1000)+(m*60*1000)+(s*1000)+ms))
}


start_ts=10:33:56.104
stop_ts=10:36:10.102


START=$(ts_get_msec $start_ts)
STOP=$(ts_get_msec $stop_ts)
DIFF=$((STOP-START))


min=$((DIFF/(60*1000)))
sec=$(((DIFF%(60*1000))/1000))
ms=$(((DIFF%(60*1000))%1000))


echo "${min}:${sec}.$ms"
% start=$(date +%s)
% echo "Diff: $(date -d @$(($(date +%s)-$start)) +"%M minutes %S seconds")"
Diff: 00 minutes 11 seconds

下面是Daniel Kamil Kozar的回答,表示小时/分钟/秒:

echo "Duration: $(($DIFF / 3600 )) hours $((($DIFF % 3600) / 60)) minutes $(($DIFF % 60)) seconds"

所以完整的脚本是:

date1=$(date +"%s")
date2=$(date +"%s")
DIFF=$(($date2-$date1))
echo "Duration: $(($DIFF / 3600 )) hours $((($DIFF % 3600) / 60)) minutes $(($DIFF % 60)) seconds"

这里有一些魔法:

time1=14:30
time2=$( date +%H:%M ) # 16:00
diff=$(  echo "$time2 - $time1"  | sed 's%:%+(1/60)*%g' | bc -l )
echo $diff hours
# outputs 1.5 hours

sed用要转换为1/60的公式替换:。然后是由bc进行的时间计算

截至目前(GNU coreutils) 7.4,你现在可以使用-d来做算术:

$ date -d -30days
Sat Jun 28 13:36:35 UTC 2014


$ date -d tomorrow
Tue Jul 29 13:40:55 UTC 2014

你可以使用的单位是天、年、月、小时、分钟和秒:

$ date -d tomorrow+2days-10minutes
Thu Jul 31 13:33:02 UTC 2014

或者把它包起来一点

alias timerstart='starttime=$(date +"%s")'
alias timerstop='echo seconds=$(($(date +"%s")-$starttime))'

这样就行了。

timerstart; sleep 2; timerstop
seconds=2

我知道这是一篇较老的文章,但我今天在编写一个脚本时偶然发现了它,该脚本将从日志文件中获取日期和时间并计算delta。下面的脚本当然是多余的,我强烈建议检查我的逻辑和数学。

#!/bin/bash


dTime=""
tmp=""


#firstEntry="$(head -n 1 "$LOG" | sed 's/.*] \([0-9: -]\+\).*/\1/')"
firstEntry="2013-01-16 01:56:37"
#lastEntry="$(tac "$LOG" | head -n 1 | sed 's/.*] \([0-9: -]\+\).*/\1/')"
lastEntry="2014-09-17 18:24:02"


# I like to make the variables easier to parse
firstEntry="${firstEntry//-/ }"
lastEntry="${lastEntry//-/ }"
firstEntry="${firstEntry//:/ }"
lastEntry="${lastEntry//:/ }"


# remove the following lines in production
echo "$lastEntry"
echo "$firstEntry"


# compute days in last entry
for i in `seq 1 $(echo $lastEntry|awk '{print $2}')`; do {
case "$i" in
1|3|5|7|8|10|12 )
dTime=$(($dTime+31))
;;
4|6|9|11 )
dTime=$(($dTime+30))
;;
2 )
dTime=$(($dTime+28))
;;
esac
} done


# do leap year calculations for all years between first and last entry
for i in `seq $(echo $firstEntry|awk '{print $1}') $(echo $lastEntry|awk '{print $1}')`; do {
if [ $(($i%4)) -eq 0 ] && [ $(($i%100)) -eq 0 ] && [ $(($i%400)) -eq 0 ]; then {
if [ "$i" = "$(echo $firstEntry|awk '{print $1}')" ] && [ $(echo $firstEntry|awk '{print $2}') -lt 2 ]; then {
dTime=$(($dTime+1))
} elif [ $(echo $firstEntry|awk '{print $2}') -eq 2 ] && [ $(echo $firstEntry|awk '{print $3}') -lt 29 ]; then {
dTime=$(($dTime+1))
} fi
} elif [ $(($i%4)) -eq 0 ] && [ $(($i%100)) -ne 0 ]; then {
if [ "$i" = "$(echo $lastEntry|awk '{print $1}')" ] && [ $(echo $lastEntry|awk '{print $2}') -gt 2 ]; then {
dTime=$(($dTime+1))
} elif [ $(echo $lastEntry|awk '{print $2}') -eq 2 ] && [ $(echo $lastEntry|awk '{print $3}') -ne 29 ]; then {
dTime=$(($dTime+1))
} fi
} fi
} done


# substract days in first entry
for i in `seq 1 $(echo $firstEntry|awk '{print $2}')`; do {
case "$i" in
1|3|5|7|8|10|12 )
dTime=$(($dTime-31))
;;
4|6|9|11 )
dTime=$(($dTime-30))
;;
2 )
dTime=$(($dTime-28))
;;
esac
} done


dTime=$(($dTime+$(echo $lastEntry|awk '{print $3}')-$(echo $firstEntry|awk '{print $3}')))


# The above gives number of days for sample. Now we need hours, minutes, and seconds
# As a bit of hackery I just put the stuff in the best order for use in a for loop
dTime="$(($(echo $lastEntry|awk '{print $6}')-$(echo $firstEntry|awk '{print $6}'))) $(($(echo $lastEntry|awk '{print $5}')-$(echo $firstEntry|awk '{print $5}'))) $(($(echo $lastEntry|awk '{print $4}')-$(echo $firstEntry|awk '{print $4}'))) $dTime"
tmp=1
for i in $dTime; do {
if [ $i -lt 0 ]; then {
case "$tmp" in
1 )
tmp="$(($(echo $dTime|awk '{print $1}')+60)) $(($(echo $dTime|awk '{print $2}')-1))"
dTime="$tmp $(echo $dTime|awk '{print $3" "$4}')"
tmp=1
;;
2 )
tmp="$(($(echo $dTime|awk '{print $2}')+60)) $(($(echo $dTime|awk '{print $3}')-1))"
dTime="$(echo $dTime|awk '{print $1}') $tmp $(echo $dTime|awk '{print $4}')"
tmp=2
;;
3 )
tmp="$(($(echo $dTime|awk '{print $3}')+24)) $(($(echo $dTime|awk '{print $4}')-1))"
dTime="$(echo $dTime|awk '{print $1" "$2}') $tmp"
tmp=3
;;
esac
} fi
tmp=$(($tmp+1))
} done


echo "The sample time is $(echo $dTime|awk '{print $4}') days, $(echo $dTime|awk '{print $3}') hours, $(echo $dTime|awk '{print $2}') minutes, and $(echo $dTime|awk '{print $1}') seconds."

您将得到如下输出。

2012 10 16 01 56 37
2014 09 17 18 24 02
The sample time is 700 days, 16 hours, 27 minutes, and 25 seconds.

我修改了一点脚本,使其独立(即。只是设置变量值),但也许总体思想也是如此。您可能需要对负值进行额外的错误检查。

要测量经过的时间(以秒为单位),我们需要:

  • 表示经过的秒数和的整数
  • 一种将这种整数转换为可用格式的方法。

以秒为单位的整数值:

  • 有两种bash内部方法可以为经过的秒数找到整数值:

    1. Bash变量SECONDS(如果SECONDS未设置,则失去其特殊属性)。

      • 设置SECONDS的值为0:

        SECONDS=0
        sleep 1  # Process to execute
        elapsedseconds=$SECONDS
        
      • Storing the value of the variable SECONDS at the start:

        a=$SECONDS
        sleep 1  # Process to execute
        elapsedseconds=$(( SECONDS - a ))
        
    2. Bash printf option %(datefmt)T:

      a="$(TZ=UTC0 printf '%(%s)T\n' '-1')"    ### `-1`  is the current time
      sleep 1                                  ### Process to execute
      elapsedseconds=$(( $(TZ=UTC0 printf '%(%s)T\n' '-1') - a ))
      

Convert such integer to an usable format

The bash internal printf can do that directly:

$ TZ=UTC0 printf '%(%H:%M:%S)T\n' 12345
03:25:45

类似的

$ elapsedseconds=$((12*60+34))
$ TZ=UTC0 printf '%(%H:%M:%S)T\n' "$elapsedseconds"
00:12:34

这将在持续时间超过24小时时失败,因为我们实际上打印的是wallclock时间,而不是真正的持续时间:

$ hours=30;mins=12;secs=24
$ elapsedseconds=$(( ((($hours*60)+$mins)*60)+$secs ))
$ TZ=UTC0 printf '%(%H:%M:%S)T\n' "$elapsedseconds"
06:12:24

对于细节爱好者,来自bash-hackers.org:

%(FORMAT)T输出使用FORMAT生成的日期-时间字符串 作为strftime(3)的格式字符串。相关联的参数是 自时代开始的秒数,或-1(当前时间)或-2 (shell 启动时间)。如果没有提供相应的参数,则当前

. Time为默认值

所以你可能只想调用textifyDuration $elpasedseconds,其中textifyDuration是持续时间打印的另一个实现:

textifyDuration() {
local duration=$1
local shiff=$duration
local secs=$((shiff % 60));  shiff=$((shiff / 60));
local mins=$((shiff % 60));  shiff=$((shiff / 60));
local hours=$shiff
local splur; if [ $secs  -eq 1 ]; then splur=''; else splur='s'; fi
local mplur; if [ $mins  -eq 1 ]; then mplur=''; else mplur='s'; fi
local hplur; if [ $hours -eq 1 ]; then hplur=''; else hplur='s'; fi
if [[ $hours -gt 0 ]]; then
txt="$hours hour$hplur, $mins minute$mplur, $secs second$splur"
elif [[ $mins -gt 0 ]]; then
txt="$mins minute$mplur, $secs second$splur"
else
txt="$secs second$splur"
fi
echo "$txt (from $duration seconds)"
}

GNU日期。

为了获得格式化的时间,我们应该以多种方式使用外部工具(GNU date)来获得几乎一年的长度,包括纳秒。

数学里面的日期。

不需要外部算术,在date中一步完成:

date -u -d "0 $FinalDate seconds - $StartDate seconds" +"%H:%M:%S"

是的,在命令字符串中有一个0零。这是需要的。

这是假设你可以将date +"%T"命令更改为date +"%s"命令,这样值将以秒为单位存储(打印)。

注意,该命令仅限于:

  • $StartDate$FinalDate秒为正值。
  • $FinalDate中的值比$StartDate大(稍后)。
  • 时差小于24小时。
  • 您接受带有小时、分钟和秒的输出格式。很容易改变。
  • 使用-u UTC时间是可以接受的。避免“DST”和本地时间的修正。

如果你必须使用10:33:56字符串,那么,只需将其转换为seconds,
此外,秒这个词可以缩写为sec:

string1="10:33:56"
string2="10:36:10"
StartDate=$(date -u -d "$string1" +"%s")
FinalDate=$(date -u -d "$string2" +"%s")
date -u -d "0 $FinalDate sec - $StartDate sec" +"%H:%M:%S"

请注意,秒时间转换(如上所述)是相对于“这”一天(今天)的开始。


这个概念可以扩展到纳秒,就像这样:

string1="10:33:56.5400022"
string2="10:36:10.8800056"
StartDate=$(date -u -d "$string1" +"%s.%N")
FinalDate=$(date -u -d "$string2" +"%s.%N")
date -u -d "0 $FinalDate sec - $StartDate sec" +"%H:%M:%S.%N"

如果需要计算更长的(最多364天)时差,则必须使用(某)年的开始作为参考,并使用格式值%j(一年中的天数):

类似于:

string1="+10 days 10:33:56.5400022"
string2="+35 days 10:36:10.8800056"
StartDate=$(date -u -d "2000/1/1 $string1" +"%s.%N")
FinalDate=$(date -u -d "2000/1/1 $string2" +"%s.%N")
date -u -d "2000/1/1 $FinalDate sec - $StartDate sec" +"%j days %H:%M:%S.%N"


Output:
026 days 00:02:14.340003400
遗憾的是,在这种情况下,我们需要手动从天数中减去1 ONE。 date命令将一年中的第一天显示为1。 没那么难…

a=( $(date -u -d "2000/1/1 $FinalDate sec - $StartDate sec" +"%j days %H:%M:%S.%N") )
a[0]=$((10#${a[0]}-1)); echo "${a[@]}"
< p > < br > < br > 长秒数的使用是有效的,并记录在这里:
https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html#Examples-of-date < / p >

Busybox日期

一个用于小型设备的工具(一个非常小的可执行文件):Busybox。

创建一个名为date的busybox链接:

$ ln -s /bin/busybox date

然后通过调用date来使用它(将其放置在包含PATH的目录中)。

或者像这样做一个别名:

$ alias date='busybox date'

Busybox date有一个很好的选项:-D来接收输入时间的格式。 这就打开了很多格式来作为时间。 使用-D选项,我们可以直接转换时间10:33:56:

date -D "%H:%M:%S" -d "10:33:56" +"%Y.%m.%d-%H:%M:%S"

从上面的命令输出中可以看到,一天被假设为“today”。获取纪元开始的时间:

$ string1="10:33:56"
$ date -u -D "%Y.%m.%d-%H:%M:%S" -d "1970.01.01-$string1" +"%Y.%m.%d-%H:%M:%S"
1970.01.01-10:33:56

Busybox date甚至可以接收没有-D的时间(以上面的格式):

$ date -u -d "1970.01.01-$string1" +"%Y.%m.%d-%H:%M:%S"
1970.01.01-10:33:56

输出格式甚至可以是epoch之后的秒。

$ date -u -d "1970.01.01-$string1" +"%s"
52436

对于这两次,还有一点bash数学(busybox还不能做数学运算):

string1="10:33:56"
string2="10:36:10"
t1=$(date -u -d "1970.01.01-$string1" +"%s")
t2=$(date -u -d "1970.01.01-$string2" +"%s")
echo $(( t2 - t1 ))

或格式:

$ date -u -D "%s" -d "$(( t2 - t1 ))" +"%H:%M:%S"
00:02:14

我需要一个时差脚本用于mencoder(它的--endpos是相对的),我的解决方案是调用一个Python脚本:

$ ./timediff.py 1:10:15 2:12:44
1:02:29

还支持秒的分数:

$ echo "diff is `./timediff.py 10:51.6 12:44` (in hh:mm:ss format)"
diff is 0:01:52.4 (in hh:mm:ss format)

它可以告诉你200和120的差值是1h 20m:

$ ./timediff.py 120:0 200:0
1:20:0

并且可以将任何(可能是分数)秒、分或小时数转换为hh:mm:ss

$ ./timediff.py 0 3600
1:00:0
$ ./timediff.py 0 3.25:0:0
3:15:0

timediff.py:

#!/usr/bin/python


import sys


def x60(h,m):
return 60*float(h)+float(m)


def seconds(time):
try:
h,m,s = time.split(':')
return x60(x60(h,m),s)
except ValueError:
try:
m,s = time.split(':')
return x60(m,s)
except ValueError:
return float(time)


def difftime(start, end):
d = seconds(end) - seconds(start)
print '%d:%02d:%s' % (d/3600,d/60%60,('%02f' % (d%60)).rstrip('0').rstrip('.'))


if __name__ == "__main__":
difftime(sys.argv[1],sys.argv[2])

使用GNU units:

$ units
2411 units, 71 prefixes, 33 nonlinear units
You have: (10hr+36min+10s)-(10hr+33min+56s)
You want: s
* 134
/ 0.0074626866
You have: (10hr+36min+10s)-(10hr+33min+56s)
You want: min
* 2.2333333
/ 0.44776119

这是我的bash实现(bit从其他SO;-)

function countTimeDiff() {
timeA=$1 # 09:59:35
timeB=$2 # 17:32:55


# feeding variables by using read and splitting with IFS
IFS=: read ah am as <<< "$timeA"
IFS=: read bh bm bs <<< "$timeB"


# Convert hours to minutes.
# The 10# is there to avoid errors with leading zeros
# by telling bash that we use base 10
secondsA=$((10#$ah*60*60 + 10#$am*60 + 10#$as))
secondsB=$((10#$bh*60*60 + 10#$bm*60 + 10#$bs))
DIFF_SEC=$((secondsB - secondsA))
echo "The difference is $DIFF_SEC seconds.";


SEC=$(($DIFF_SEC%60))
MIN=$((($DIFF_SEC-$SEC)%3600/60))
HRS=$((($DIFF_SEC-$MIN*60)/3600))
TIME_DIFF="$HRS:$MIN:$SEC";
echo $TIME_DIFF;
}


$ countTimeDiff 2:15:55 2:55:16
The difference is 2361 seconds.
0:39:21

未测试,可能有bug。

另一个选项是使用dateutils (http://www.fresse.org/dateutils/#datediff)中的datediff:

$ datediff 10:33:56 10:36:10
134s
$ datediff 10:33:56 10:36:10 -f%H:%M:%S
0:2:14
$ datediff 10:33:56 10:36:10 -f%0H:%0M:%0S
00:02:14

你也可以使用gawkmawk 1.3.4也有strftimemktime,但旧版本的mawknawk没有。

$ TZ=UTC0 awk 'BEGIN{print strftime("%T",mktime("1970 1 1 10 36 10")-mktime("1970 1 1 10 33 56"))}'
00:02:14

或者这里有另一种使用GNU date的方法:

$ date -ud@$(($(date -ud'1970-01-01 10:36:10' +%s)-$(date -ud'1970-01-01 10:33:56' +%s))) +%T
00:02:14

date可以给你的差异和格式为你(OS X选项显示)

date -ujf%s $(($(date -jf%T "10:36:10" +%s) - $(date -jf%T "10:33:56" +%s))) +%T
# 00:02:14


date -ujf%s $(($(date -jf%T "10:36:10" +%s) - $(date -jf%T "10:33:56" +%s))) \
+'%-Hh %-Mm %-Ss'
# 0h 2m 14s

某些字符串处理可以删除这些空值

date -ujf%s $(($(date -jf%T "10:36:10" +%s) - $(date -jf%T "10:33:56" +%s))) \
+'%-Hh %-Mm %-Ss' | sed "s/[[:<:]]0[hms] *//g"
# 2m 14s

如果你把较早的时间放在前面,这是行不通的。如果你需要处理它,将$(($(date ...) - $(date ...)))改为$(echo $(date ...) - $(date ...) | bc | tr -d -)

下面是一个只使用date命令功能的解决方案,使用"ago",而不使用第二个变量来存储完成时间:

#!/bin/bash


# save the current time
start_time=$( date +%s.%N )


# tested program
sleep 1


# the current time after the program has finished
# minus the time when we started, in seconds.nanoseconds
elapsed_time=$( date +%s.%N --date="$start_time seconds ago" )


echo elapsed_time: $elapsed_time

这给:

$ ./time_elapsed.sh
elapsed_time: 1.002257120

使用GNU日期(可靠的Ubuntu 14.04 LTS)概括@nisetama的解决方案:

start=`date`
# <processing code>
stop=`date`
duration=`date -ud@$(($(date -ud"$stop" +%s)-$(date -ud"$start" +%s))) +%T`


echo $start
echo $stop
echo $duration

收益率:

Wed Feb 7 12:31:16 CST 2018
Wed Feb 7 12:32:25 CST 2018
00:01:09
#!/bin/bash


START_TIME=$(date +%s)


sleep 4


echo "Total time elapsed: $(date -ud "@$(($(date +%s) - $START_TIME))" +%T) (HH:MM:SS)"
$ ./total_time_elapsed.sh
Total time elapsed: 00:00:04 (HH:MM:SS)

定义这个函数(在~/.bashrc中):

time::clock() {
[ -z "$ts" ]&&{ ts=`date +%s%N`;return;}||te=`date +%s%N`
printf "%6.4f" $(echo $((te-ts))/1000000000 | bc -l)
unset ts te
}

现在你可以测量部分脚本的时间了:

$ cat script.sh
# ... code ...
time::clock
sleep 0.5
echo "Total time: ${time::clock}"
# ... more code ...


$ ./script.sh
Total time: 0.5060

对于发现执行瓶颈非常有用。

如果你已经计算了时间差,并且它们小于1天,这里是bc的一个非常边缘的用例,以某种方式将输出格式化为

  • HH MM SS.xxxx

24-hour格式,记住小数点右边的数字打印在< >强base 60 < / >强

jot -w 'obase = 60; %.3f' - 1.3219567 300 73.6543211 | bc
       01.19 19
01 14.58 33
02 28.37 51
03 42.17 06  # 3 mins 42 secs
04 55.56 20
...
...
19 38 10.54 32  # 19 hrs 38 mins 10 secs
#
# (or 7:38pm, if it's representing absolute time)

但这绝对是一个快速得到大致数字的方法