Я печатаю начальное и конечное время с помощью date +"%T"
, что приводит к чему-то вроде:
10:33:56
10:36:10
Как я мог рассчитать и распечатать разницу между этими двумя?
Я хотел бы получить что-то вроде:
2m 14s
Я печатаю начальное и конечное время с помощью date +"%T"
, что приводит к чему-то вроде:
10:33:56
10:36:10
Как я мог рассчитать и распечатать разницу между этими двумя?
Я хотел бы получить что-то вроде:
2m 14s
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.
Чтобы измерить прошедшее время (в секундах) нам нужно:
Существует два внутренних способа bash найти целое значение количества прошедших секунд:
Переменная Bash SECONDS (если SECONDS не установлена, она теряет свое специальное свойство).
Установка значения SECONDS на 0:
SECONDS=0
sleep 1 # Process to execute
elapsedseconds=$SECONDS
Сохранение значения переменной 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
но это не удастся в течение более 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)
. Связанный аргумент количество секунд, прошедших с эпохи, или -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) несколькими способами, чтобы получить почти годичный период, включая наносекунды.
Нет необходимости во внешней арифметике, сделайте все это за один шаг внутри date
:
date -u -d "0 $FinalDate seconds - $StartDate seconds" +"%H:%M:%S"
Да, в командной строке есть ноль 0
. Это необходимо.
Это при условии, что вы можете изменить команду date +"%T"
на команду date +"%s"
, чтобы значения сохранялись (печатались) в считанные секунды.
Обратите внимание, что команда ограничена:
$StartDate
и $FinalDate
секунд.$FinalDate
больше (позже), чем $StartDate
.Если вы должны использовать строку 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 с именем date:
$ ln -s /bin/busybox date
Затем используйте его, вызвав этот date
(поместите его в каталог, включенный в PATH).
Или создайте псевдоним:
$ 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
Дата занятой ячейки может даже получать время (в формате выше) без -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
Вот как я это сделал:
START=$(date +%s);
sleep 1; # Your stuff
END=$(date +%s);
echo $((END-START)) | awk '{print int($1/60)":"int($1%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 [email protected]$(($(date -ud'1970-01-01 10:36:10' +%s)-$(date -ud'1970-01-01 10:33:56' +%s))) +%T
00:02:14
Я хотел бы предложить другой способ, чтобы избежать вызова команды 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"
Вот какая магия:
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"
Таким образом, полный script будет:
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
Вот решение, использующее только команды 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
% start=$(date +%s)
% echo "Diff: $(date -d @$(($(date +%s)-$start)) +"%M minutes %S seconds")"
Diff: 00 minutes 11 seconds
Мне понадобилась разница во времени script для использования с mencoder
(ее --endpos
относительна), и мое решение вызовите Python script:
$ ./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])
С 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 -)
Я понимаю, что это более старая статья, но сегодня я перешел к ней во время работы над script, который будет принимать даты и время из файла журнала и вычислять дельта. Ниже приведено script ниже, и я настоятельно рекомендую проверить свою логику и математику.
#!/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.
Я немного изменил script, чтобы сделать его автономным (т.е. просто установить значения переменных), но, возможно, общая идея также встречается. Возможно, вам понадобится дополнительная проверка ошибок для отрицательных значений.
Обобщение решения @nisetama с использованием даты GNU (верный Ubuntu 14.04 LTS):
start='date'
# <processing code>
stop='date'
duration='date [email protected]$(($(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 должна быть в маленьком регистре. вот пример.
[[email protected] ~]# date1=$(date +"%s"); date
Wed Feb 21 23:00:20 MYT 2018
[[email protected] ~]#
[[email protected] ~]# date2=$(date +"%s"); date
Wed Feb 21 23:00:33 MYT 2018
[[email protected] ~]#
[[email protected] ~]# diff=$(($date2-$date1))
[[email protected] ~]#
Предыдущая переменная была объявлена в нижнем регистре. это то, что произошло, когда используется верхний регистр.
[[email protected] ~]# echo "Duration: $(($DIFF / 3600 )) hours $((($DIFF % 3600) / 60)) minutes $(($DIFF % 60)) seconds"
-bash: / 3600 : syntax error: operand expected (error token is "/ 3600 ")
[[email protected] ~]#
Итак, быстрое исправление будет выглядеть так
[[email protected] ~]# echo "Duration: $(($diff / 3600 )) hours $((($diff % 3600) / 60)) minutes $(($diff % 60)) seconds"
Duration: 0 hours 0 minutes 13 seconds
[[email protected] ~]#
#!/bin/bash
START_TIME=$(date +%s)
sleep 4
echo "Total time elapsed: $(date -ud "@$(($(date +%s) - $START_TIME))" +%T) (HH:MM:SS)"
$ ./total_time_elapsed.sh
Total time elapsed: 00:00:04 (HH:MM:SS)
Здесь моя реализация 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
Не проверено, может быть ошибкой.