Как рассчитать разницу во времени в bash-скрипт?



я печатаю время начала и окончания с помощью date +"%T", что приводит к чему-то вроде:



10:33:56
10:36:10


как я могу рассчитать и распечатать разницу между этими двумя?



Я хотел бы сделать что-то вроде:



2m 14s
2874   18  

18 ответов:

Bash имеет удобный SECONDS встроенная переменная, которая отслеживает количество секунд, прошедших с момента запуска оболочки. Эта переменная сохраняет свои свойства при назначении, и значение, возвращаемое после назначения, является числом секунд с момента назначения плюс назначенное значение.

таким образом, вы можете просто установить SECONDS до 0 перед началом события timed, просто прочитайте SECONDS после события, и сделать арифметику времени до отображающий.

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

как это решение не зависит от date +%s (который является расширением GNU), он переносится на все системы, поддерживаемые Bash.

секунд

для измерения прошедшего времени (в секундах) нам нужно:

  • целое число, представляющее число секунд, прошедших и
  • способ преобразования такого целого числа в полезный формат.

целочисленное значение прошедших секунд:

  • есть два внутренних способа bash найти целое значение для количества прошедших секунд:

    1. Баш переменная секунды (если секунды не заданы, она теряет свое специальное свойство).

      • установка значения секунд в 0:

        SECONDS=0
        sleep 1  # Process to execute
        elapsedseconds=$SECONDS
        
      • сохранение значения переменной SECONDS в начале:

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

преобразовать такое целое число в полезный формат

Баш внутренний 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

но это не удастся для длительностей более 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

для любителей деталей, от bash-hackers.org:

%(FORMAT)T выводит строку даты и времени, полученную в результате использования формата как строка формата для strftime(3). Этот связанный аргумент количество секунд с момента эпохи, или -1 (текущее время) или -2 (оболочки время запуска.) Если соответствующий аргумент не указан, то текущий время используется по умолчанию.

так что вы можете просто позвонить textifyDuration $elpasedseconds здесь textifyDuration еще одна реализация печати продолжительности:

textifyDuration() {
   local duration=
   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" команда a date +"%s" команда таким образом, значения будут сохранены (напечатаны) в секундах.

обратите внимание, что команда ограничена к:

  • положительные значения $StartDate и $FinalDate секунд.
  • значение $FinalDate больше (позже), чем $StartDate.
  • разница во времени менее 24 часов.
  • вы принимаете выходной формат с часами, минутами и секундами. Очень легко изменить.
  • допустимо использовать-U UTC раз. Чтобы избежать" DST " и коррекции местного времени.

если вы должны использовать элемент 10:33:56 строка, ну, просто преобразовать его в секундах,
кроме того, слово секунды может быть сокращено как 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[@]}"



Использование длинного числа секунд является допустимым и документированным here:
https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html#Examples-of-date


Busybox дата

инструмент, используемый в небольших устройствах (очень маленький исполняемый файл для установки): Busybox.

либо сделать ссылку на busybox называется дата:

$ ln -s /bin/busybox date

используйте его тогда, называя это date (поместите его в каталог, включенный в путь).

или псевдоним например:

$ alias date='busybox date'

Busybox дата имеет хороший вариант: - D, чтобы получить формат времени ввода. Это открывает много форматов, которые будут использоваться в качестве времени. Используя опцию-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

Busybox дата может даже получить время (в формате выше) без - Д:

$ 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

для обоих раз, и немного Баш математика (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

вот как я сделал это:

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

очень просто, возьмите количество секунд в начале, а затем взять количество секунд в конце, и распечатать разницу в минутах:секунд.

другой вариант-использовать datediff С dateutils (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

Я хотел бы предложить другой способ, чтобы не вспоминать . Это может быть полезно в случае, если вы уже собрали метки времени в дата:

ts_get_sec()
{
  read -r h m s <<< $(echo  | 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  | 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"

вот какая магия:

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 заменяет a : с формулой для преобразования в 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

следуя ответу Даниэля Камила Козара, чтобы показать часы / минуты / секунды:

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"

или завернуть его немного

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

тогда это работает.

timerstart; sleep 2; timerstop
seconds=2
% start=$(date +%s)
% echo "Diff: $(date -d @$(($(date +%s)-$start)) +"%M minutes %S seconds")"
Diff: 00 minutes 11 seconds

С 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

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 возможности команд с использованием "назад", а не с помощью второй переменной для хранения времени окончания:

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

Я понимаю, что это более старый пост, но я наткнулся на него сегодня, работая над сценарием, который будет принимать даты и время из файла журнала и вычислять дельту. Скрипт это конечно перебор, и я настоятельно рекомендую проверить мою логику и математику.

#!/bin/bash

dTime=""
tmp=""

#firstEntry="$(head -n 1 "$LOG" | sed 's/.*] \([0-9: -]\+\).*//')"
firstEntry="2013-01-16 01:56:37"
#lastEntry="$(tac "$LOG" | head -n 1 | sed 's/.*] \([0-9: -]\+\).*//')"
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 }')`; 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 }') $(echo $lastEntry|awk '{print }')`; do {
  if [ $(($i%4)) -eq 0 ] && [ $(($i%100)) -eq 0 ] && [ $(($i%400)) -eq 0 ]; then {
    if [ "$i" = "$(echo $firstEntry|awk '{print }')" ] && [ $(echo $firstEntry|awk '{print }') -lt 2 ]; then {
      dTime=$(($dTime+1))
    } elif [ $(echo $firstEntry|awk '{print }') -eq 2 ] && [ $(echo $firstEntry|awk '{print }') -lt 29 ]; then {
      dTime=$(($dTime+1))
    } fi
  } elif [ $(($i%4)) -eq 0 ] && [ $(($i%100)) -ne 0 ]; then {
    if [ "$i" = "$(echo $lastEntry|awk '{print }')" ] && [ $(echo $lastEntry|awk '{print }') -gt 2 ]; then {
      dTime=$(($dTime+1))
    } elif [ $(echo $lastEntry|awk '{print }') -eq 2 ] && [ $(echo $lastEntry|awk '{print }') -ne 29 ]; then {
      dTime=$(($dTime+1))
    } fi
  } fi
} done

# substract days in first entry
for i in `seq 1 $(echo $firstEntry|awk '{print }')`; 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 }')-$(echo $firstEntry|awk '{print }')))

# 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 }')-$(echo $firstEntry|awk '{print }'))) $(($(echo $lastEntry|awk '{print }')-$(echo $firstEntry|awk '{print }'))) $(($(echo $lastEntry|awk '{print }')-$(echo $firstEntry|awk '{print }'))) $dTime"
tmp=1
for i in $dTime; do {
  if [ $i -lt 0 ]; then {
    case "$tmp" in
     1 )
      tmp="$(($(echo $dTime|awk '{print }')+60)) $(($(echo $dTime|awk '{print }')-1))"
      dTime="$tmp $(echo $dTime|awk '{print " "}')"
      tmp=1
      ;;
     2 )
      tmp="$(($(echo $dTime|awk '{print }')+60)) $(($(echo $dTime|awk '{print }')-1))"
      dTime="$(echo $dTime|awk '{print }') $tmp $(echo $dTime|awk '{print }')"
      tmp=2
      ;;
     3 )
      tmp="$(($(echo $dTime|awk '{print }')+24)) $(($(echo $dTime|awk '{print }')-1))"
      dTime="$(echo $dTime|awk '{print " "}') $tmp"
      tmp=3
      ;;
    esac
  } fi
  tmp=$(($tmp+1))
} done

echo "The sample time is $(echo $dTime|awk '{print }') days, $(echo $dTime|awk '{print }') hours, $(echo $dTime|awk '{print }') minutes, and $(echo $dTime|awk '{print }') 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.

Я немного изменил скрипт, чтобы сделать его автономным (т. е. просто установите значения переменных), но, возможно, общая идея также встречается. Возможно, вы захотите некоторые дополнительные ошибки проверки на отрицательные значения.

мне нужен был сценарий разницы во времени для использования с 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 составляет 1 ч 20 м:

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

и может конвертировать любое (возможно, дробное) количество секунд или минут или часов в чч:мм:СС

$ ./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])

обобщение решения @nisetama с использованием GNU date (trusty Ubuntu 14.04 LTS):

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

Я не могу прокомментировать ответ mcaleaa, поэтому я публикую это здесь. Переменная "diff" должна быть на малом регистре. Вот вам пример.

[root@test ~]# date1=$(date +"%s"); date
Wed Feb 21 23:00:20 MYT 2018
[root@test ~]# 
[root@test ~]# date2=$(date +"%s"); date
Wed Feb 21 23:00:33 MYT 2018
[root@test ~]# 
[root@test ~]# diff=$(($date2-$date1))
[root@test ~]# 

предыдущая переменная была объявлена в нижнем регистре. Это то, что произошло, когда верхний регистр используется.

[root@test ~]# echo "Duration: $(($DIFF / 3600 )) hours $((($DIFF % 3600) / 60)) minutes $(($DIFF % 60)) seconds"
-bash: / 3600 : syntax error: operand expected (error token is "/ 3600 ")
[root@test ~]# 

Итак, быстрое исправление будет похоже на это

[root@test ~]# echo "Duration: $(($diff / 3600 )) hours $((($diff % 3600) / 60)) minutes $(($diff % 60)) seconds"
Duration: 0 hours 0 minutes 13 seconds
[root@test ~]# 

вот моя реализация bash (с битами, взятыми из других SO ; -)

function countTimeDiff() {
    timeA= # 09:59:35
    timeB= # 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

не проверено, может быть багги.

Comments

    Ничего не найдено.