如何计算bash脚本中的时差?

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

 10:33:56 10:36:10 

我怎么能计算和打印这两者之间的差异?

我想得到像这样的东西:

 2m 14s 

Bash有一个方便的SECONDS内buildvariables,用于跟踪从shell启动以来经过的秒数。 此variables在分配给它时保留其属性,赋值后返回的值是从赋值加上赋值后的秒数。

因此,您可以在启动定时事件之前将SECONDS设置为0,只需在事件之后读取SECONDS ,然后在显示之前进行时间运算。

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

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

为了测量stream逝的时间(以秒为单位),我们需要:

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

经过秒数的整数值:

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

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

      • 将SECONDS的值设置为0:

         SECONDS=0 sleep 1 # Process to execute elapsedseconds=$SECONDS 
      • 在开始时存储variablesSECONDS的值:

         a=$SECONDS sleep 1 # Process to execute elapsedseconds=$(( SECONDS - a )) 
    2. Bash printf选项%(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 )) 

将这种整数转换为可用的格式

bash内部的printf可以直接做到这一点:

 $ 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 

GNUdate。

为了得到合适的时间,我们应该用几种方法使用外部工具(GNUdate)来达到将近一年的时间,包括纳秒。

math里面的date。

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

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

是的,在命令string中有一个0零。 这是必需的。

假设您可以将date +"%T"命令更改为date +"%s"命令,以便将值存储(打印)在几秒钟内。

请注意,该命令仅限于:

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

如果您必须使用10:33:56string,那么只需将其转换为秒,
另外,秒可以缩写为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 。 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[@]}" 

使用长秒数是有效的,并在这里logging:
https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html#Examples-of-date


忙碌的date

在较小的设备(一个非常小的可执行文件安装)使用的工具:Busybox。

或者build立一个叫做datebox的链接:

 $ ln -s /bin/busybox date 

使用它然后通过调用这个date (把它放在一个PATH包含的目录中)。

或者打个别名:

 $ alias date='busybox date' 

忙碌的date有一个很好的select:-D接收input时间的格式。 这开辟了许多格式作为时间使用。 使用-D选项,我们可以直接转换时间10:33:56:

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

正如你从上面命令的输出中可以看到的那样,假定这一天是“今天”。 为了获得从时代开始的时间:

 $ 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 

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

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

输出格式甚至可能是自纪元以来的秒数。

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

对于这两个时间,和一个小小的bashmath(busybox不能做math,但):

 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 

我是这样做的:

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

真的很简单,就是在开始的秒数,然后采取最后秒数,并以分钟打印差异:秒。

我想提出另一种避免调用date命令的方法。 如果您已经以%Tdate格式收集时间戳,则可能会有所帮助:

 ts_get_sec() { read -rhms <<< $(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 -rhms 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" 

另一个select是使用dateutils datediffhttp://www.fresse.org/dateutils/#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 

这里有一些魔力:

 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 

sedreplace:用公式转换为1/60。 然后是由bc做的时间计算

截至date(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 
 % start=$(date +%s) % echo "Diff: $(date -d @$(($(date +%s)-$start)) +"%M minutes %S seconds")" Diff: 00 minutes 11 seconds 

或者把它包起来

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

然后这工作。

 timerstart; sleep 2; timerstop seconds=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 

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

我意识到这是一个较老的post,但我今天在它的脚本上做了一些工作,从日志文件中获取date和时间,并计算出增量。 下面的脚本当然是矫枉过正,我强烈build议检查我的逻辑和math。

 #!/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. 

我修改了一下脚本,使其成为独立的(即只是设置variables值),但也许总体思路也是如此。 你可能会想要一些额外的错误检查负值。

我需要一个用于mencoder的时间差异脚本(它的--endpos是相对的),我的解决scheme是调用一个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]) 

这里是我的bash实现(从其他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 

没有testing,可能是越野车。

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 

一些string处理可以删除这些空值

 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 -)

这是一个解决scheme,只使用date命令function使用“前”,而不是使用第二个variables来存储结束时间:

 #!/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