Jak obliczyć czas, który upłynął w skrypcie bash?


224

Czas rozpoczęcia i zakończenia drukuję za pomocą date +"%T", co powoduje coś takiego:

10:33:56
10:36:10

Jak mogę obliczyć i wydrukować różnicę między tymi dwoma?

Chciałbym dostać coś takiego:

2m 14s

5
Z drugiej strony, czy nie możesz użyć timepolecenia?
anishsane,

Zamiast tego użyj czasu uniksowego date +%s, a następnie odejmij, aby uzyskać różnicę w sekundach.
roblogic 21.04.17

Odpowiedzi:


476

Bash ma przydatną SECONDSwbudowaną zmienną, która śledzi liczbę sekund, które upłynęły od uruchomienia powłoki. Ta zmienna zachowuje swoje właściwości po przypisaniu, a wartość zwracana po przypisaniu jest liczbą sekund od przypisania plus przypisana wartość.

Dlatego możesz po prostu ustawić SECONDS0 przed rozpoczęciem zdarzenia czasowego, po prostu przeczytać SECONDSpo zdarzeniu i wykonać arytmetykę czasu przed wyświetleniem.

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

Ponieważ to rozwiązanie nie zależy od date +%s(które jest rozszerzeniem GNU), jest przenośne dla wszystkich systemów obsługiwanych przez Bash.


59
+1. Nawiasem mówiąc, możesz oszukać datew wykonaniu arytmetyki czasu, pisząc date -u -d @"$diff" +'%-Mm %-Ss'. (To interpretuje $diffjako sekundę od epoki i oblicza minuty i sekundy w UTC). Prawdopodobnie nie jest to już bardziej eleganckie, po prostu lepiej zaciemnione. :-P
ruakh

13
Ładne i proste. Jeśli ktoś potrzebuje godzin:echo "$(($diff / 3600)) hours, $((($diff / 60) % 60)) minutes and $(($diff % 60)) seconds elapsed."
chus

6
Należy przeczytać, $(date -u +%s)aby zapobiec wyścigowi w dniach, w których zmienia się czas letni. I strzeżcie się złych sekund przestępnych;)
Tino

3
@ daniel-kamil-kozar Nice find. Jest to jednak trochę kruche. Jest tylko jedna taka zmienna, więc nie można jej używać rekurencyjnie do mierzenia wydajności, a nawet gorzej po unset SECONDSjej zniknięciu:echo $SECONDS; unset SECONDS; SECONDS=0; sleep 3; echo $SECONDS
Tino,

6
@ParthianShot: Przykro mi, że tak uważasz. Uważam jednak, że ta odpowiedź jest najbardziej potrzebna podczas wyszukiwania odpowiedzi na pytanie takie jak to: mierzyć czas między zdarzeniami, a nie obliczać czas.
Daniel Kamil Kozar

99

sekundy

Do pomiaru upływu czasu (w sekundach) potrzebujemy:

  • liczba całkowita reprezentująca liczbę upływających sekund i
  • sposób na konwersję takiej liczby całkowitej na użyteczny format.

Liczba całkowita upływających sekund:

  • Istnieją dwa wewnętrzne sposoby na znalezienie wartości całkowitej dla liczby upływających sekund:

    1. Zmienna Bash SECONDS (jeśli nie ustawiono SECONDS, traci swoją specjalną właściwość).

      • Ustawienie wartości SECONDS na 0:

        SECONDS=0
        sleep 1  # Process to execute
        elapsedseconds=$SECONDS
      • Przechowywanie wartości zmiennej SECONDSna początku:

        a=$SECONDS
        sleep 1  # Process to execute
        elapsedseconds=$(( SECONDS - a ))
    2. Opcja 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 ))

Konwertuj taką liczbę całkowitą na użyteczny format

Bash wewnętrzny printfmoże to zrobić bezpośrednio:

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

podobnie

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

ale to nie powiedzie się na dłużej niż 24 godziny, ponieważ tak naprawdę drukujemy czas naścienny, a nie czas trwania:

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

Dla miłośników szczegółów z bash-hackers.org :

%(FORMAT)Twyświetla ciąg daty i godziny wynikający z użycia FORMATU jako ciągu formatu dla strftime(3). Powiązany argument to liczba sekund od Epoki lub -1 (bieżący czas) lub -2 (czas uruchamiania powłoki). Jeśli nie podano odpowiedniego argumentu, domyślnie jest używany bieżący czas.

Możesz więc zadzwonić, textifyDuration $elpasedsecondsgdzie textifyDurationjest jeszcze jedna implementacja drukowania czasu trwania:

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

Data GNU.

Aby uzyskać sformatowany czas, powinniśmy skorzystać z zewnętrznego narzędzia (data GNU) na kilka sposobów, aby osiągnąć długość prawie roku, w tym nanosekundy.

Matematyka w dacie.

Nie ma potrzeby stosowania zewnętrznej arytmetyki, zrób to wszystko w jednym kroku date:

date -u -d "0 $FinalDate seconds - $StartDate seconds" +"%H:%M:%S"

Tak, 0w wierszu polecenia jest zero. To jest potrzebne.

Zakłada się, że możesz zmienić date +"%T"polecenie na date +"%s"polecenie, aby wartości zostały zapisane (wydrukowane) w kilka sekund.

Pamiętaj, że polecenie jest ograniczone do:

  • Wartości dodatnie $StartDatei $FinalDatesekundy.
  • Wartość w $FinalDatejest większa (później) niż $StartDate.
  • Różnica czasu mniejsza niż 24 godziny.
  • Akceptujesz format wyjściowy z godzinami, minutami i sekundami. Bardzo łatwa do zmiany.
  • Dopuszczalne jest użycie czasów -u UTC. Aby uniknąć „DST” i korekt czasu lokalnego.

Jeśli musisz użyć 10:33:56ciągu, cóż, po prostu przekonwertuj go na sekundy,
również słowo sekundy może być skrócone jako 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"

Zwróć uwagę, że przeliczenie czasu w sekundach (jak pokazano powyżej) odnosi się do początku „tego” dnia (dziś).


Pojęcie to można rozszerzyć na nanosekundy, takie jak to:

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"

Jeśli wymagane jest obliczenie dłuższych (do 364 dni) różnic czasowych, musimy użyć początku (jakiegoś) roku jako wartości odniesienia i wartości formatu %j(numeru dnia w roku):

Podobny do:

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

Niestety, w tym przypadku musimy ręcznie odjąć 1JEDEN od liczby dni. Polecenie daty wyświetla pierwszy dzień roku jako 1. Nie jest to trudne ...

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



Wykorzystanie dużej liczby sekund jest prawidłowe i udokumentowane tutaj:
https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html#Examples-of-date


Data zajętości

Narzędzie używane w mniejszych urządzeniach (bardzo mały plik wykonywalny do zainstalowania): Busybox.

Utwórz link do busyboksa o nazwie data:

$ ln -s /bin/busybox date

Użyj go następnie, wywołując to date(umieść w katalogu zawierającym ŚCIEŻKĘ).

Lub stwórz alias:

$ alias date='busybox date'

Data Busybox ma fajną opcję: -D, aby otrzymać format godziny wejściowej. Otwiera to wiele formatów, które można wykorzystać jako czas. Za pomocą opcji -D możemy bezpośrednio przekonwertować czas 10:33:56:

date -D "%H:%M:%S" -d "10:33:56" +"%Y.%m.%d-%H:%M:%S"

I jak widać na podstawie powyższego polecenia, przyjmuje się, że dzień jest „dzisiaj”. Aby uzyskać czas zaczynający się od epoki:

$ 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

Data Busybox może nawet odbierać czas (w powyższym formacie) bez -D:

$ date -u -d "1970.01.01-$string1" +"%Y.%m.%d-%H:%M:%S"
1970.01.01-10:33:56

Format wyjściowy może wynosić nawet kilka sekund od epoki.

$ date -u -d "1970.01.01-$string1" +"%s"
52436

W obu przypadkach i trochę matematyki (zajęty nie potrafi jeszcze matematyki):

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

Lub sformatowane:

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

6
Ta niesamowita odpowiedź obejmowała wiele podstaw i zawierała dokładne wyjaśnienia. Dziękuję Ci!
Devy

Musiałem sprawdzić %(FORMAT)Tciąg znaków przekazany do printfpolecenia zintegrowanego z bash. Z bash-hackers.org : %(FORMAT)Twyświetl ciąg daty i godziny wynikający z użycia FORMATjako ciągu formatu strftime (3). Powiązany argument to liczba sekund od Epoki lub -1 (bieżący czas) lub -2 (czas uruchamiania powłoki). Jeśli nie podano odpowiedniego argumentu, domyślnie jest używany bieżący czas . To ezoteryczne. W tym przypadku %spodana strftime(3)jest „liczba sekund od epoki”.
David Tonhofer

35

Oto jak to zrobiłem:

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

Naprawdę proste, weź liczbę sekund na początku, a następnie weź liczbę sekund na końcu i wydrukuj różnicę w minutach: sekundach.


6
Czym to się różni od mojego rozwiązania? Naprawdę nie widzę korzyści z wywołania awkw tym przypadku, ponieważ Bash równie dobrze radzi sobie z arytmetyką liczb całkowitych.
Daniel Kamil Kozar

1
Twoja odpowiedź też jest poprawna. Niektórzy ludzie, jak ja, wolą pracować z awk niż z niekonsekwencjami bash.
Dorian

2
Czy mógłbyś rozwinąć nieco więcej na temat niespójności Basha dotyczących arytmetyki liczb całkowitych? Chciałbym dowiedzieć się więcej na ten temat, ponieważ nic nie wiedziałem.
Daniel Kamil Kozar

12
Właśnie tego szukałem. Nie rozumiem krytyki tej odpowiedzi. Lubię widzieć więcej niż jedno rozwiązanie problemu. A ja jestem tym, który woli awkkomendy bash (jeśli nic poza tym, ponieważ awk działa w innych powłokach). Bardziej podobało mi się to rozwiązanie. Ale to moja osobista opinia.
rpsml

4
Zera wiodące: echo $ ((KONIEC-START)) | awk '{printf "% 02d:% 02d \ n", int (1/60 $), int (1% 60)}'
Jon Strayer

17

Inną opcją jest skorzystanie datediffz 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

Możesz także użyć gawk. mawk1.3.4 posiada również strftimea mktimejednak starsze wersje mawki nawknie.

$ TZ=UTC0 awk 'BEGIN{print strftime("%T",mktime("1970 1 1 10 36 10")-mktime("1970 1 1 10 33 56"))}'
00:02:14

Lub oto inny sposób, aby to zrobić z 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

1
Szukałem czegoś podobnego do twojej date metody GNU . Geniusz.
Haohmaru,

Proszę usunąć mój komentarz ... przepraszam
Bill Gale

Po instalacji dateutilsprzez apt, nie było datediffkomendy - musiałem użyć dateutils.ddiff.
Suzana

12

Chciałbym zaproponować inny sposób, który pozwoli uniknąć przypominania date polecenia. Może to być pomocne w przypadku, gdy zebrałeś już znaczniki czasu w %Tformacie daty:

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"

możemy nawet obsługiwać milisekundy w ten sam sposób.

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"

1
Czy istnieje sposób na obsługę milisekund, np. 10: 33: 56.104
Umesh Rajbhandari

Obsługa milisekund źle zachowuje się, jeśli pole milisekund zaczyna się od zera. Na przykład ms = „033” da końcowe cyfry „027”, ponieważ pole ms jest interpretowane jako ósemkowe. zmiana „echo” ... ”+ ms)) na…” + $ {ms ## + (0)})) ”naprawi to tak długo, jak długo„ shopt -s extglob ”pojawi się gdzieś powyżej tego w scenariusz. Prawdopodobnie należy również usunąć wiodące zera od h, m i s ...
Eric Towers

3
echo $(((60*60*$((10#$h)))+(60*$((10#$m)))+$((10#$s))))pracował dla mnie odkąd dostałemvalue too great for base (error token is "08")
Niklas

6

Oto magia:

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

sedzastępuje a :formułą do konwersji na 1/60. Następnie dokonuje się obliczenia czasubc


5

Na dzień dzisiejszy (GNU coreutils) 7.4 możesz teraz używać -d do arytmetyki:

$ date -d -30days
Sat Jun 28 13:36:35 UTC 2014

$ date -d tomorrow
Tue Jul 29 13:40:55 UTC 2014

Jednostki, których możesz użyć, to dni, lata, miesiące, godziny, minuty i sekundy:

$ date -d tomorrow+2days-10minutes
Thu Jul 31 13:33:02 UTC 2014

3

Kontynuując odpowiedź Daniela Kamila Kozara, aby pokazać godziny / minuty / sekundy:

echo "Duration: $(($DIFF / 3600 )) hours $((($DIFF % 3600) / 60)) minutes $(($DIFF % 60)) seconds"

Pełny skrypt byłby więc:

date1=$(date +"%s")
date2=$(date +"%s")
diff=$(($date2-$date1))
echo "Duration: $(($DIFF / 3600 )) hours $((($DIFF % 3600) / 60)) minutes $(($DIFF % 60)) seconds"

3

Lub trochę zawiń

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

To działa.

timerstart; sleep 2; timerstop
seconds=2

3

Oto rozwiązanie wykorzystujące tylko datemożliwości poleceń za pomocą „temu” i nie wykorzystujące drugiej zmiennej do przechowywania czasu zakończenia:

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

to daje:

$ ./time_elapsed.sh 
elapsed_time: 1.002257120

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

6
Pomocne byłoby wiedzieć, co robi ta odpowiedź, czego nie robią inne odpowiedzi.
Louis,

Umożliwia łatwe wyświetlanie czasu trwania w dowolnym dopuszczalnym formacie date.
Ryan C. Thompson

2

date może dać ci różnicę i sformatować ją dla ciebie (pokazane opcje 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

Niektóre przetwarzanie łańcuchów może usunąć te puste wartości

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

To nie zadziała, jeśli umieścisz wcześniej. Jeśli musisz sobie z tym poradzić, zmień $(($(date ...) - $(date ...)))na$(echo $(date ...) - $(date ...) | bc | tr -d -)


1

Potrzebowałem skryptu różnicy czasu do użycia z mencoder( --endposjest względny), a moim rozwiązaniem jest wywołanie skryptu Python:

$ ./timediff.py 1:10:15 2:12:44
1:02:29

obsługiwane są również ułamki sekund:

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

i może powiedzieć, że różnica między 200 a 120 wynosi 1h 20m:

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

i może konwertować dowolną (prawdopodobnie ułamkową) liczbę sekund lub minut lub godzin na 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])

1

Z 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

1

Uogólnienie rozwiązania @ nisetama przy użyciu daty GNU (zaufane 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

wydajność:

Wed Feb 7 12:31:16 CST 2018
Wed Feb 7 12:32:25 CST 2018
00:01:09

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

1

Zdefiniuj tę funkcję (powiedzmy w ~ / .bashrc):

time::clock() {
    [ -z "$ts" ]&&{ ts=`date +%s%N`;return;}||te=`date +%s%N`
    printf "%6.4f" $(echo $((te-ts))/1000000000 | bc -l)
    unset ts te
}

Teraz możesz mierzyć czas części swoich skryptów:

$ cat script.sh
# ... code ...
time::clock
sleep 0.5
echo "Total time: ${time::clock}"
# ... more code ...

$ ./script.sh
Total time: 0.5060

bardzo przydatne, aby znaleźć wąskie gardła wykonania.


0

Zdaję sobie sprawę, że to starszy post, ale natknąłem się na niego dzisiaj, pracując nad skryptem, który pobierałby daty i godziny z pliku dziennika i obliczał różnicę. Poniższy skrypt jest z pewnością przesadny i bardzo polecam sprawdzenie mojej logiki i matematyki.

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

Otrzymasz wynik w następujący sposób.

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.

Zmodyfikowałem trochę skrypt, aby był samodzielny (tj. Po prostu ustawiał wartości zmiennych), ale być może pojawia się też ogólny pomysł. Warto sprawdzić dodatkowe błędy pod kątem wartości ujemnych.


Ojej, dużo pracy !! Proszę spojrzeć na moją odpowiedź: stackoverflow.com/a/24996647/2350426

0

Nie mogę skomentować odpowiedzi mcaleaa, dlatego zamieszczam to tutaj. Zmienna „diff” powinna znajdować się na małych literach. Oto przykład.

[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 ~]# 

Poprzednia zmienna została zadeklarowana małymi literami. Tak się stało, gdy użyto wielkich liter.

[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 ~]# 

Tak więc szybka naprawa byłaby taka

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

-1

Oto moja implementacja bash (z bitami pobranymi z innych 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

Nie testowany, może być wadliwy.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.