Solución:
Bash tiene un útil SECONDS
variable incorporada que rastrea el número de segundos que han pasado desde que se inició el shell. Esta variable conserva sus propiedades cuando se asigna a, y el valor devuelto después de la asignación es el número de segundos desde la asignación más el valor asignado.
Por lo tanto, puede configurar SECONDS
a 0 antes de iniciar el evento cronometrado, simplemente lea SECONDS
después del evento, y haga la aritmética de tiempo antes de mostrar.
SECONDS=0
# do some work
duration=$SECONDS
echo "$(($duration / 60)) minutes and $(($duration % 60)) seconds elapsed."
Como esta solución no depende de date +%s
(que es una extensión GNU), es portátil para todos los sistemas compatibles con Bash.
Segundos
Para medir el tiempo transcurrido (en segundos) necesitamos:
- un número entero que representa el recuento de segundos transcurridos y
- una forma de convertir dicho número entero a un formato utilizable.
Un valor entero de segundos transcurridos:
-
Hay dos formas internas de bash para encontrar un valor entero para el número de segundos transcurridos:
-
Bash variable SECONDS (si SECONDS no está configurada, pierde su propiedad especial).
-
Estableciendo el valor de SEGUNDOS en 0:
SECONDS=0 sleep 1 # Process to execute elapsedseconds=$SECONDS
-
Almacenar el valor de la variable
SECONDS
al principio:a=$SECONDS sleep 1 # Process to execute elapsedseconds=$(( SECONDS - a ))
-
-
Opción Bash printf
%(datefmt)T
:a="$(TZ=UTC0 printf '%(%s)Tn' '-1')" ### `-1` is the current time sleep 1 ### Process to execute elapsedseconds=$(( $(TZ=UTC0 printf '%(%s)Tn' '-1') - a ))
-
Convierta dicho número entero a un formato utilizable
El bash interno printf
puede hacer eso directamente:
$ TZ=UTC0 printf '%(%H:%M:%S)Tn' 12345
03:25:45
similar
$ elapsedseconds=$((12*60+34))
$ TZ=UTC0 printf '%(%H:%M:%S)Tn' "$elapsedseconds"
00:12:34
pero esto fallará por duraciones de más de 24 horas, ya que en realidad imprimimos una hora de reloj de pared, no realmente una duración:
$ hours=30;mins=12;secs=24
$ elapsedseconds=$(( ((($hours*60)+$mins)*60)+$secs ))
$ TZ=UTC0 printf '%(%H:%M:%S)Tn' "$elapsedseconds"
06:12:24
Para los amantes de los detalles, de bash-hackers.org:
%(FORMAT)T
genera la cadena de fecha y hora resultante de usar FORMAT como una cadena de formato parastrftime(3)
. El argumento asociado es el número de segundos desde Epoch, o -1 (tiempo actual) o -2 (tiempo de inicio del shell). Si no se proporciona ningún argumento correspondiente, la hora actual se utiliza por defecto.
Entonces es posible que desee simplemente llamar textifyDuration $elpasedseconds
dónde textifyDuration
es otra implementación de la impresión de duración:
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)"
}
Fecha GNU.
Para obtener el tiempo formateado, deberíamos usar una herramienta externa (fecha GNU) de varias maneras para obtener hasta casi un año de duración e incluir nanosegundos.
Matemáticas dentro de la fecha.
No hay necesidad de aritmética externa, hágalo todo en un solo paso adentro date
:
date -u -d "0 $FinalDate seconds - $StartDate seconds" +"%H:%M:%S"
Si, hay un 0
cero en la cadena de comando. Es necesario.
Eso es asumiendo que podrías cambiar el date +"%T"
comando a un date +"%s"
comando para que los valores se almacenen (impriman) en segundos.
Tenga en cuenta que el comando se limita a:
- Valores positivos de
$StartDate
y$FinalDate
segundos. - El valor en
$FinalDate
es más grande (más tarde en el tiempo) que$StartDate
. - Diferencia horaria inferior a 24 horas.
- Acepta un formato de salida con Horas, Minutos y Segundos. Muy fácil de cambiar.
- Es aceptable utilizar -u horas UTC. Para evitar “DST” y correcciones de la hora local.
Si tu debe utilizar el 10:33:56
cadena, bueno, conviértala en segundos,
Además, la palabra segundos se podría abreviar como 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"
Tenga en cuenta que la conversión de tiempo en segundos (como se presentó arriba) es relativa al inicio de “este” día (Hoy).
El concepto podría extenderse a nanosegundos, así:
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"
Si se requiere calcular diferencias de tiempo más largas (hasta 364 días), debemos usar el inicio de (algún) año como referencia y el valor de formato %j
(el número de día del año):
Similar 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
Lamentablemente, en este caso, debemos restar manualmente 1
UNO del número de días. El comando de fecha ve el primer día del año como 1. No es tan difícil …
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[@]}"
El uso de una gran cantidad de segundos es válido y está documentado aquí:
https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html#Examples-of-date
Fecha de Busybox
Una herramienta utilizada en dispositivos más pequeños (un ejecutable muy pequeño para instalar): Busybox.
Haga un enlace a busybox llamado fecha:
$ ln -s /bin/busybox date
Úselo entonces llamando a esto date
(colóquelo en un directorio incluido PATH).
O crea un alias como:
$ alias date="busybox date"
Busybox date tiene una buena opción: -D para recibir el formato de la hora de entrada. Eso abre muchos formatos para usar como tiempo. Usando la opción -D podemos convertir la hora 10:33:56 directamente:
date -D "%H:%M:%S" -d "10:33:56" +"%Y.%m.%d-%H:%M:%S"
Y como puede ver en el resultado del comando anterior, se supone que el día es “hoy”. Para obtener el tiempo a partir de la época:
$ 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 fecha de Busybox puede incluso recibir la hora (en el formato anterior) sin -D:
$ date -u -d "1970.01.01-$string1" +"%Y.%m.%d-%H:%M:%S"
1970.01.01-10:33:56
Y el formato de salida podría incluso ser segundos desde la época.
$ date -u -d "1970.01.01-$string1" +"%s"
52436
Para ambas ocasiones, y un poco de matemáticas bash (busybox no puede hacer las matemáticas, todavía):
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 formateado:
$ date -u -D "%s" -d "$(( t2 - t1 ))" +"%H:%M:%S"
00:02:14
Así es como lo hice:
START=$(date +%s);
sleep 1; # Your stuff
END=$(date +%s);
echo $((END-START)) | awk '{print int($1/60)":"int($1%60)}'
Realmente simple, tome el número de segundos al principio, luego tome el número de segundos al final e imprima la diferencia en minutos: segundos.