如何计算bash脚本的时差?

Mis*_*hko 210 bash date

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

10:33:56
10:36:10
Run Code Online (Sandbox Code Playgroud)

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

我想得到类似的东西:

2m 14s
Run Code Online (Sandbox Code Playgroud)

Dan*_*zar 428

Bash有一个方便的SECONDS内置变量,用于跟踪自shell启动以来经过的秒数.此变量在分配时保留其属性,赋值后返回的值是自分配后的秒数加上指定的值.

因此,您可以SECONDS在启动定时事件之前设置为0,只需SECONDS在事件之后读取,并在显示之前执行时间算术.

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

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

  • +1.顺便说一句,你可以通过写'date -u -d @"$ diff"+'% - Mm%-Ss'来欺骗'date`来为你执行时间算术.(它将'$ diff`解释为自纪元以来的秒数,并计算UTC中的分钟和秒数.)但这可能不再优雅,但更好的混淆.:-P (56认同)
  • 很好,很简单.如果有人需要几个小时:`echo'$(($ diff/3600))小时,$(($ diff/60)%60))分钟和$(($ diff%60))秒过去." (11认同)
  • @ParthianShot:对不起,你这么认为.但是,我相信这个答案是大多数人在寻找像这样的问题的答案时所需要的:衡量事件之间经过的时间,而不是计算时间. (5认同)
  • 应该读取`$(date -u +%s)`以防止在夏令时切换的日期出现竞争条件.并提防邪恶的闰秒;) (4认同)
  • @ daniel-kamil-kozar很高兴找到。但是,这有点脆弱。只有一个这样的变量,因此不能递归地使用它来衡量性能,甚至更糟的是,在“未设置SECONDS”之后,它消失了:“ echo $ SECONDS;”。未设置秒数;SECONDS = 0;睡3; 回声$ SECONDS` (3认同)
  • 绝对是我见过的最简单、最智能的 bash 计时器功能。非常感谢。 (3认同)
  • @saianudeep 它不起作用,因为 Alpine 默认情况下没有 Bash,而是 busybox 提供的 ash。这是一个关于 Bash 的问题,答案显然使用了 Bash 专有的设施。这正是您应该在脚本的 shebang 中明确说明 Bash 用法的原因。 (3认同)
  • @Tino:在最初的答案将近四年之后,我发现根本不需要在这里使用`date`。那好吧... (2认同)

小智 83

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

  • 一个整数,表示经过的秒数和
  • 一种将此类整数转换为可用格式的方法.

经过秒的整数值:

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

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

    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 ))
      
      Run Code Online (Sandbox Code Playgroud)

将此类整数转换为可用格式

bash内部printf可以直接执行此操作:

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

同样

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

但这会持续超过24小时,因为我们实际打印的是挂钟时间,而不是真正的持续时间:

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

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

%(FORMAT)T输出使用FORMAT作为格式字符串的日期时间字符串strftime(3).关联参数是自Epoch以来的秒数,或-1(当前时间)或-2(shell启动时间).如果未提供相应的参数,则将当前时间用作默认值.

所以,你可能只想调用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)"
}
Run Code Online (Sandbox Code Playgroud)

GNU日期.

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

数学里面的日期.

无需外部算术,只需一步即可完成所有操作date:

date -u -d "0 $FinalDate seconds - $StartDate seconds" +"%H:%M:%S"
Run Code Online (Sandbox Code Playgroud)

是的,0命令字符串中有一个零.这是必需的.

假设您可以将date +"%T"命令更改为命令,date +"%s"以便以秒为单位存储(打印)值.

请注意,该命令仅限于:

  • 正值$StartDate$FinalDate秒.
  • 值in $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"
Run Code Online (Sandbox Code Playgroud)

请注意,秒时间转换(如上所示)是相对于"this"日(今天)的开始.


这个概念可以扩展到纳秒,如下所示:

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"
Run Code Online (Sandbox Code Playgroud)

如果需要计算更长(最多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
Run Code Online (Sandbox Code Playgroud)

遗憾的是,在这种情况下,我们需要1从天数中手动减去ONE.日期命令查看一年的第一天为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[@]}"
Run Code Online (Sandbox Code Playgroud)



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


Busybox日期

用于较小设备的工具(要安装的非常小的可执行文件):Busybox.

要么设置一个名为datebox的busybox链接:

$ ln -s /bin/busybox date
Run Code Online (Sandbox Code Playgroud)

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

或者创建一个别名:

$ alias date='busybox date'
Run Code Online (Sandbox Code Playgroud)

Busybox日期有一个很好的选择:-D接收输入时间的格式.这开辟了许多格式作为时间使用.使用-D选项我们可以直接转换时间10:33:56:

date -D "%H:%M:%S" -d "10:33:56" +"%Y.%m.%d-%H:%M:%S"
Run Code Online (Sandbox Code Playgroud)

正如您从上面命令的输出中看到的那样,假设这一天是"今天".要获得从纪元开始的时间:

$ 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
Run Code Online (Sandbox Code Playgroud)

Busybox日期甚至可以在没有-D的情况下收到时间(格式如上):

$ date -u -d "1970.01.01-$string1" +"%Y.%m.%d-%H:%M:%S"
1970.01.01-10:33:56
Run Code Online (Sandbox Code Playgroud)

从纪元开始,输出格式甚至可能是秒.

$ date -u -d "1970.01.01-$string1" +"%s"
52436
Run Code Online (Sandbox Code Playgroud)

对于这两个时间,以及一点点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 ))
Run Code Online (Sandbox Code Playgroud)

或格式化:

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

  • 这是一个如此惊人的答案,涵盖了很多基础和精细的解释.谢谢! (5认同)

Dor*_*ian 34

我是这样做的:

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

非常简单,在开始时采用秒数,然后在结束时采用秒数,并以分钟为单位打印差异:秒.

  • 我只是在寻找这个.我不明白对这个答案的批评.我希望看到一个问题的解决方案.并且,我更喜欢使用`awk`命令进行bash(如果没有别的,因为awk在其他shell中工作).我更喜欢这个解决方案.但这是我个人的意见. (10认同)
  • 与我的解决方案有什么不同?在这种情况下,我真的看不到从调用`awk`中获益,因为Bash同样处理整数运算. (6认同)
  • 前导零:echo $((END-START))| awk'{printf"%02d:%02d \n",int($ 1/60),int($ 1%60)}' (4认同)
  • 你的答案也是正确的。有些人,比如我,更喜欢使用 awk 而不是 bash 的不一致之处。 (3认同)
  • 你能详细说明Bash中有关整数运算的不一致吗?我想更多地了解这一点,因为我不知道. (2认同)

nis*_*ama 14

另一种选择是使用datediffdateutils(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
Run Code Online (Sandbox Code Playgroud)

你也可以用gawk.mawk1.3.4也有strftime,mktime但旧版本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
Run Code Online (Sandbox Code Playgroud)

或者这是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
Run Code Online (Sandbox Code Playgroud)


Zsk*_*dan 12

我想提出另一种避免召回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"
Run Code Online (Sandbox Code Playgroud)

我们甚至可以用同样的方式处理毫秒数.

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"
Run Code Online (Sandbox Code Playgroud)

  • `echo $(((60*60*$((10#$ h)))+(60*$((10#$ m)))+ $((10#$ s))))`为我工作因为我得到了'对于基础来说价值太大(错误令牌是"08")` (3认同)

Zol*_* K. 7

这是一个仅使用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
Run Code Online (Sandbox Code Playgroud)

这给出:

$ ./time_elapsed.sh

elapsed_time: 1.002257120
Run Code Online (Sandbox Code Playgroud)


red*_*ent 6

这里有一些魔力:

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
Run Code Online (Sandbox Code Playgroud)

sed:公式替换a 转换为1/60.然后是时间计算bc


mca*_*eaa 5

继 Daniel Kamil Kozar 的回答之后,显示小时/分钟/秒:

echo "Duration: $(($DIFF / 3600 )) hours $((($DIFF % 3600) / 60)) minutes $(($DIFF % 60)) seconds"
Run Code Online (Sandbox Code Playgroud)

所以完整的脚本将是:

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


rag*_*ags 5

截至目前(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
Run Code Online (Sandbox Code Playgroud)

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

$ date -d tomorrow+2days-10minutes
Thu Jul 31 13:33:02 UTC 2014
Run Code Online (Sandbox Code Playgroud)