Come calcolare la differenza di tempo nello script di bash?

Stampo l’ora di inizio e di fine utilizzando la date +"%T" , che si traduce in qualcosa di simile:

 10:33:56 10:36:10 

Come potrei calcolare e stampare la differenza tra questi due?

Mi piacerebbe ottenere qualcosa come:

 2m 14s 

Bash ha una comoda variabile incorporata SECONDS che tiene traccia del numero di secondi trascorsi dall’inizio della shell. Questa variabile mantiene le sue proprietà quando è assegnata a, e il valore restituito dopo che l’assegnazione è il numero di secondi dall’assegnazione più il valore assegnato.

Pertanto, è ansible impostare SECONDS su 0 prima di avviare l’evento a tempo, leggere semplicemente SECONDS dopo l’evento e eseguire l’aritmetica temporale prima della visualizzazione.

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

Poiché questa soluzione non dipende dalla date +%s (che è un’estensione GNU), è portatile per tutti i sistemi supportati da Bash.

secondi

Per misurare il tempo trascorso (in secondi) abbiamo bisogno di:

  • un numero intero che rappresenta il conteggio dei secondi trascorsi e
  • un modo per convertire tale numero intero in un formato utilizzabile.

Un valore intero di secondi trascorsi:

  • Esistono due modi interni di bash per trovare un valore intero per il numero di secondi trascorsi:

    1. Bash variabile SECONDS (se SECONDS è disinserito perde la sua proprietà speciale).

      • Impostazione del valore di SECONDI su 0:

         SECONDS=0 sleep 1 # Process to execute elapsedseconds=$SECONDS 
      • Memorizzare il valore della variabile SECONDS all’inizio:

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

Converti tale numero intero in un formato utilizzabile

La bash printf interna può farlo direttamente:

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

allo stesso modo

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

ma questo fallirà per una durata di oltre 24 ore, dato che stampiamo effettivamente un tempo di wallclock, non proprio una durata:

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

Per gli amanti del dettaglio, da bash-hackers.org :

%(FORMAT)T restituisce la stringa data-time risultante dall’uso di FORMAT come stringa di formato per strftime(3) . L’argomento associato è il numero di secondi da Epoch o -1 (ora corrente) o -2 (tempo di avvio della shell). Se non viene fornito alcun argomento corrispondente, l’ora corrente viene utilizzata come predefinita.

Quindi potresti voler semplicemente chiamare textifyDuration $elpasedseconds dove textifyDuration è un’altra implementazione della stampa di durata:

 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.

Per ottenere il tempo necessario, dovremmo usare uno strumento esterno (data GNU) in vari modi per ottenere fino a quasi un anno di lunghezza e inclusi i nanosecondi.

Math entro la data.

Non c’è bisogno di calcoli aritmetici esterni, fai tutto in un’unica fase della date :

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

Sì, c’è uno zero 0 nella stringa di comando. È necessario.

Ciò presuppone che è ansible modificare il comando date +"%T" in un comando date +"%s" modo che i valori vengano memorizzati (stampati) in secondi.

Si noti che il comando è limitato a:

  • Valori positivi di $StartDate e $FinalDate secondi.
  • Il valore in $FinalDate è maggiore (più avanti nel tempo) rispetto a $StartDate .
  • Differenza di tempo inferiore a 24 ore.
  • Accetti un formato di output con Ore, minuti e secondi. Molto facile da cambiare.
  • È accettabile utilizzare -u volte UTC. Per evitare “DST” e correzioni orarie locali.

Se devi usare la stringa 10:33:56 , beh, basta convertirla in secondi,
inoltre, la parola secondi potrebbe essere abbreviata come se:

 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" 

Si noti che la conversione del tempo dei secondi (come presentato sopra) è relativa all’inizio del giorno “questo” (Oggi).


Il concetto potrebbe essere esteso a nanosecondi, come questo:

 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" 

Se è necessario calcolare le differenze temporali più lunghe (fino a 364 giorni), dobbiamo utilizzare l’inizio di (alcuni) anni come riferimento e il valore del formato %j (il numero del giorno nell’anno):

Simile a:

 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 

Purtroppo, in questo caso, dobbiamo sottrarre manualmente 1 UNO dal numero di giorni. Il comando date visualizza il primo giorno dell’anno come 1. Non così difficile …

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

L’uso di un numero lungo di secondi è valido e documentato qui:
https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html#Examples-of-date


Data di consegna

Uno strumento utilizzato in dispositivi più piccoli (un eseguibile molto piccolo da installare): Busybox.

O fare un link a busybox chiamato data:

 $ ln -s /bin/busybox date 

Usalo quindi chiamando questa date (inseriscilo in una directory inclusa PATH).

Oppure fai un alias come:

 $ alias date='busybox date' 

La data di Busybox ha una buona opzione: -D per ricevere il formato del tempo di input. Questo apre molti formati da utilizzare come tempo. Usando l’opzione -D possiamo convertire direttamente il tempo 10:33:56:

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

E come puoi vedere dall’output del Comando sopra, si presume che il giorno sia “oggi”. Per ottenere il tempo che inizia in epoca:

 $ 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 

La data di Busybox può anche ricevere l’ora (nel formato sopra) senza -D:

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

E il formato di output potrebbe anche essere secondi dall’epoca.

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

Per entrambe le volte e un po ‘di matematica bash (busybox non può ancora fare la matematica):

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

O formattato:

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

Ecco come l’ho fatto:

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

Davvero semplice, prendi il numero di secondi all’inizio, quindi prendi il numero di secondi alla fine e stampa la differenza in minuti: secondi.

Un’altra opzione è usare datediff da 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 

Puoi anche usare gawk . mawk 1.3.4 ha anche strftime e mktime ma le versioni precedenti di mawk e nawk no.

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

O ecco un altro modo per farlo con la date GNU:

 $ 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 

Mi piacerebbe proporre un altro modo per evitare il richiamo del comando date . Potrebbe essere utile nel caso in cui tu abbia già raccolto data e ora in formato data %T :

 ts_get_sec() { read -rhms <<< $(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" 

possiamo anche gestire millisecondi allo stesso modo.

 ts_get_msec() { read -rhms 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" 

Ecco qualche 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 

sed sostituisce a : con una formula da convertire in 1/60. Quindi il calcolo del tempo fatto da bc

A partire da data (GNU coreutils) 7.4 ora puoi usare -d per fare aritmetica:

 $ date -d -30days Sat Jun 28 13:36:35 UTC 2014 $ date -d tomorrow Tue Jul 29 13:40:55 UTC 2014 

Le unità che puoi utilizzare sono giorni, anni, mesi, ore, minuti e secondi:

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

Seguendo la risposta di Daniel Kamil Kozar, per mostrare ore / minuti / secondi:

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

Quindi la sceneggiatura completa sarebbe:

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

O avvolgi un po ‘

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

Allora questo funziona.

 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 

Con le units GNU:

 $ 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 può darti la differenza e formattarla per te (le opzioni di OS X sono mostrate)

 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 

Alcune elaborazioni di stringhe possono rimuovere quei valori vuoti

 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 

Questo non funzionerà se si posiziona prima l'ora precedente. Se devi gestirlo, cambia $(($(date ...) - $(date ...))) a $(echo $(date ...) - $(date ...) | bc | tr -d -)

Ecco una soluzione che utilizza solo le funzionalità dei comandi di date utilizzano “fa” e non utilizza una seconda variabile per memorizzare il tempo di fine:

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

questo da:

 $ ./time_elapsed.sh elapsed_time: 1.002257120 

Mi rendo conto che questo è un post più vecchio, ma l’ho incontrato oggi mentre lavoravo a uno script che prendeva le date e le ore da un file di registro e calcola il delta. La sceneggiatura qui sotto è sicuramente esagerata, e consiglio vivamente di controllare la mia logica e matematica.

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

Otterrai l’output come segue.

 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. 

Ho modificato lo script un po ‘per renderlo autonomo (cioè, ho appena impostato i valori delle variabili), ma forse l’idea generale si presenta pure. Potresti volere qualche ulteriore controllo degli errori per i valori negativi.

Avevo bisogno di uno script con differenza di orario da usare con mencoder (il suo --endpos è relativo), e la mia soluzione è chiamare uno script Python:

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

sono supportate anche frazioni di secondi:

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

e può dirti che la differenza tra 200 e 120 è 1h 20m:

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

e può convertire qualsiasi numero (probabilmente frazionario) di secondi o minuti o ore in 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]) 

Generalizzando la soluzione di @ nisetama usando la data di GNU (fidata Ubuntu 14.04 LTS):

 start=`date` #  stop=`date` duration=`date [email protected]$(($(date -ud"$stop" +%s)-$(date -ud"$start" +%s))) +%T` echo $start echo $stop echo $duration 

cedendo:

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

Non posso commentare la risposta di mcaleaa, quindi la postò qui. La variabile “diff” dovrebbe essere in piccolo caso. Ecco un esempio.

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

La variabile precedente è stata dichiarata in lettere minuscole. Questo è quello che è successo quando si usa la maiuscola.

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

Quindi, la soluzione rapida sarebbe come questa

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

Ecco la mia implementazione di bash (con bit presi da altri 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 

Non testato, potrebbe essere bacato.