如何计算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内部方法可以查找经过秒数的整数值:
-
BashvariablesSECONDS(如果SECONDS未设置,则失去其特殊属性)。
-
将SECONDS的值设置为0:
SECONDS=0 sleep 1 # Process to execute elapsedseconds=$SECONDS
-
在开始时存储variables
SECONDS
的值:a=$SECONDS sleep 1 # Process to execute elapsedseconds=$(( SECONDS - a ))
-
-
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:56
string,那么只需将其转换为秒,
另外,秒可以缩写为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
命令的方法。 如果您已经以%T
date格式收集时间戳,则可能会有所帮助:
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
datediff
( http://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
你也可以使用gawk
。 mawk
1.3.4也有strftime
和mktime
但老版本的mawk
和nawk
不能。
$ 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
sed
replace:
用公式转换为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