Как рассчитать разницу во времени в bash-скрипте? - PullRequest
198 голосов
/ 18 января 2012

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

10:33:56
10:36:10

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

Я бы хотел получить что-то вроде:

2m 14s

Ответы [ 18 ]

405 голосов
/ 18 января 2012

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

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

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

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

79 голосов
/ 12 декабря 2014

Секунды

Чтобы измерить истекшее время (в секундах), нам нужно:

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

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

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

    1. Переменная SECONDS (если SECONDS не установлена, она теряет свое особое свойство).

      • Установка значения SECONDS0:

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

        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

, но это не удастся в течение более 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 выходыстрока даты и времени, полученная в результате использования FORMAT в качестве строки формата для strftime(3).Связанный аргумент - это количество секунд с Epoch , или -1 (текущее время) или -2 (время запуска оболочки).Если соответствующий аргумент не указан, по умолчанию используется текущее время.

Так что вы можете просто вызвать 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 -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, ну, просто конвертируйте ее в секунды,
также, слово секунд может быть сокращено как сек:

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[@]}"



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


Дата Busybox

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

Либо создайте ссылку на busybox с именемdate:

$ ln -s /bin/busybox date

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

Или создайте псевдоним, например:

$ alias date='busybox date'

У даты занятой ячейки есть хорошая опция: -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

Дата занятой ячейки может даже получать время (в формате выше) без -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

Для обоих раз и немного bash math (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
32 голосов
/ 27 ноября 2013

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

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

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

14 голосов
/ 08 апреля 2017

Другой вариант - использовать 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
11 голосов
/ 28 ноября 2013

Я бы хотел предложить другой способ избежать вызова команды 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"
6 голосов
/ 20 марта 2014

Вот немного магии:

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

5 голосов
/ 28 июля 2014

На дату (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
3 голосов
/ 05 августа 2014

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

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

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

timerstart; sleep 2; timerstop
seconds=2
3 голосов
/ 03 марта 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"
2 голосов
/ 24 сентября 2017

Вот решение, использующее только возможности команд 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...