Script de bash para calcular el tiempo transcurrido

118

Estoy escribiendo un script en bash para calcular el tiempo transcurrido para la ejecución de mis comandos, considere:

STARTTIME=$(date +%s)
#command block that takes time to complete...
#........
ENDTIME=$(date +%s)
echo "It takes $($ENDTIME - $STARTTIME) seconds to complete this task..."

Supongo que mi lógica es correcta, sin embargo, termino con la siguiente impresión:

"Se necesitan unos segundos para completar esta tarea ..."

¿Hay algún problema con mi evaluación de cadenas?

Creo que las variables de bash no están tipificadas, me encantaría que, sin embargo, hubiera un método de "cadena a entero" en bash.

Michael Mao
fuente

Respuestas:

83

Ya sea $(())o $[]va a trabajar para calcular el resultado de una operación aritmética. Estás usando $()que es simplemente tomar la cadena y evaluarla como un comando. Es una distinción un poco sutil. Espero que esto ayude.

Como Tink señaló en los comentarios sobre esta respuesta, $[]está en desuso y $(())debería favorecerse.

Entidad omnipotente
fuente
7
Es posible que desee intercambiar esos dos, ya que la página de manual de bash 4.x indica que $ [] está obsoleto y se eliminará en versiones futuras.
tink
2
Gracias, no lo sabía.
OmnipotentEntity
157

Me parece muy limpio usar la variable interna "$ SECONDS"

SECONDS=0 ; sleep 10 ; echo $SECONDS

Lon Kaut
fuente
10
Solo el éxito =)
Lon Kaut
1
Necesitas éxito, usa el tuyo
Gromish
3
$SECONDSde hecho funciona para / bin / bash. No funciona para / bin / dash, el shell predeterminado en Debian y Ubuntu.
Cameron Taggart
2
La desventaja de esta solución es que solo mide segundos enteros, es decir, no se puede utilizar si necesita una precisión inferior a un segundo.
Czechnology
@Czechnology sí, si usa sleep 0.5en arriba, el resultado es a veces 0, a veces 1 (al menos por Bash 5.0.3).
jarno
52

Está intentando ejecutar el número en el ENDTIMEcomo un comando. También debería ver un error como 1370306857: command not found. En su lugar, use la expansión aritmética :

echo "It takes $(($ENDTIME - $STARTTIME)) seconds to complete this task..."

También puede guardar los comandos en un script separado commands.shy usar el comando time:

time commands.sh
perreal
fuente
28

Puede usar la timepalabra clave de Bash aquí con una cadena de formato adecuada

TIMEFORMAT='It takes %R seconds to complete this task...'
time {
    #command block that takes time to complete...
    #........
 }

Esto es lo que diceTIMEFORMAT la referencia :

El valor de este parámetro se utiliza como una cadena de formato que especifica cómo se time debe mostrar la información de tiempo para las canalizaciones con el prefijo de la palabra reservada. El carácter " %" introduce una secuencia de escape que se expande a un valor de tiempo u otra información. Las secuencias de escape y sus significados son los siguientes; las llaves denotan porciones opcionales.

%%

    A literal ‘%’.
%[p][l]R

    The elapsed time in seconds.
%[p][l]U

    The number of CPU seconds spent in user mode.
%[p][l]S

    The number of CPU seconds spent in system mode.
%P

    The CPU percentage, computed as (%U + %S) / %R. 

La p opcional es un dígito que especifica la precisión, el número de dígitos fraccionarios después de un punto decimal. Un valor de 0 hace que no se emita ningún punto decimal o fracción. Se pueden especificar como máximo tres lugares después del punto decimal; los valores de p mayores que 3 se cambian a 3. Si no se especifica p , se usa el valor 3.

El opcional lespecifica un formato más largo, incluidos los minutos, de la forma MMmSS.FFs. El valor de p determina si se incluye o no la fracción.

Si esta variable no se establece, Bash actúa como si tuviera el valor

$'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'

Si el valor es nulo, no se muestra información de tiempo. Se agrega una nueva línea al final cuando se muestra la cadena de formato.

gniourf_gniourf
fuente
10

Para números más grandes, es posible que deseemos imprimir en un formato más legible. El siguiente ejemplo hace lo mismo que otros, pero también se imprime en formato "humano":

secs_to_human() {
    if [[ -z ${1} || ${1} -lt 60 ]] ;then
        min=0 ; secs="${1}"
    else
        time_mins=$(echo "scale=2; ${1}/60" | bc)
        min=$(echo ${time_mins} | cut -d'.' -f1)
        secs="0.$(echo ${time_mins} | cut -d'.' -f2)"
        secs=$(echo ${secs}*60|bc|awk '{print int($1+0.5)}')
    fi
    echo "Time Elapsed : ${min} minutes and ${secs} seconds."
}

Prueba simple:

secs_to_human "300"
secs_to_human "305"
secs_to_human "59"
secs_to_human "60"
secs_to_human "660"
secs_to_human "3000"

Salida:

Time Elapsed : 5 minutes and 0 seconds.
Time Elapsed : 5 minutes and 5 seconds.
Time Elapsed : 0 minutes and 59 seconds.
Time Elapsed : 1 minutes and 0 seconds.
Time Elapsed : 11 minutes and 0 seconds.
Time Elapsed : 50 minutes and 0 seconds.

Para usar en un script como se describe en otras publicaciones (capturar el punto de inicio y luego llamar a la función con la hora de finalización:

start=$(date +%s)
# << performs some task here >>
secs_to_human "$(($(date +%s) - ${start}))"
Mike Q
fuente
9

Prueba el siguiente código:

start=$(date +'%s') && sleep 5 && echo "It took $(($(date +'%s') - $start)) seconds"
Bulmaro Herrera
fuente
5

Esta es una alternativa de una sola línea a la función de Mike Q:

secs_to_human() {
    echo "$(( ${1} / 3600 ))h $(( (${1} / 60) % 60 ))m $(( ${1} % 60 ))s"
}
error de servidor interno
fuente
¡Agradable! Por lo general, soy muy detallado con mi código bash, esto es genial.
Mike Q
Combinar esto con SECONDSla respuesta de Lon Kaut y tener en cuenta que $ / $ {} es innecesario en variables aritméticas hace que el código sea tan corto que incluso podría usarse en línea:echo "$((SECONDS/3600))h $(((SECONDS/60)%60))m $((SECONDS%60))s"
ssc
2

intente usar el tiempo con la opción de segundos transcurridos:

/usr/bin/time -f%e sleep 1 bajo bash.

o \time -f%e sleep 1en bash interactivo.

ver la página del manual de tiempo:

Los usuarios del shell bash deben utilizar una ruta explícita para ejecutar el comando de tiempo externo y no la variante incorporada del shell. En el sistema donde la hora está instalada en / usr / bin, el primer ejemplo se convertiría en / usr / bin / time wc / etc / hosts

y

FORMATTING THE OUTPUT
...
    %      A literal '%'.
    e      Elapsed  real  (wall  clock) time used by the process, in
                 seconds.
Linchar
fuente
1
/bin/timeno va a funcionar aquí: OP menciona un bloque . Entonces realmente necesitamos la palabra clave timeaquí.
gniourf_gniourf
-3
start=$(date +%Y%m%d%H%M%S);
for x in {1..5};
do echo $x;
sleep 1; done;
end=$(date +%Y%m%d%H%M%S);
elapsed=$(($end-$start));
ftime=$(for((i=1;i<=$((${#end}-${#elapsed}));i++));
        do echo -n "-";
        done;
        echo ${elapsed});
echo -e "Start  : ${start}\nStop   : ${end}\nElapsed: ${ftime}"

Start  : 20171108005304
Stop   : 20171108005310
Elapsed: -------------6
Rafał Białas
fuente
-3
    #!/bin/bash

    time_elapsed(){
    appstop=$1; appstart=$2

    ss_strt=${appstart:12:2} ;ss_stop=${appstop:12:2}
    mm_strt=${appstart:10:2} ;mm_stop=${appstop:10:2}
     hh_strt=${appstart:8:2} ; hh_stop=${appstop:8:2}
     dd_strt=${appstart:6:2} ; dd_stop=${appstop:6:2}
     mh_strt=${appstart:4:2} ; mh_stop=${appstop:4:2}
     yy_strt=${appstart:0:4} ; yy_stop=${appstop:0:4}

    if [ "${ss_stop}" -lt "${ss_strt}" ]; then ss_stop=$((ss_stop+60)); mm_stop=$((mm_stop-1)); fi
    if [ "${mm_stop}" -lt "0" ]; then mm_stop=$((mm_stop+60)); hh_stop=$((hh_stop-1)); fi
    if [ "${mm_stop}" -lt "${mm_strt}" ]; then mm_stop=$((mm_stop+60)); hh_stop=$((hh_stop-1)); fi
    if [ "${hh_stop}" -lt "0" ]; then hh_stop=$((hh_stop+24)); dd_stop=$((dd_stop-1)); fi
    if [ "${hh_stop}" -lt "${hh_strt}" ]; then hh_stop=$((hh_stop+24)); dd_stop=$((dd_stop-1)); fi

    if [ "${dd_stop}" -lt "0" ]; then dd_stop=$((dd_stop+$(mh_days $mh_stop $yy_stop))); mh_stop=$((mh_stop-1)); fi
    if [ "${dd_stop}" -lt "${dd_strt}" ]; then dd_stop=$((dd_stop+$(mh_days $mh_stop $yy_stop))); mh_stop=$((mh_stop-1)); fi

    if [ "${mh_stop}" -lt "0" ]; then mh_stop=$((mh_stop+12)); yy_stop=$((yy_stop-1)); fi
    if [ "${mh_stop}" -lt "${mh_strt}" ]; then mh_stop=$((mh_stop+12)); yy_stop=$((yy_stop-1)); fi

    ss_espd=$((10#${ss_stop}-10#${ss_strt})); if [ "${#ss_espd}" -le "1" ]; then ss_espd=$(for((i=1;i<=$((${#ss_stop}-${#ss_espd}));i++)); do echo -n "0"; done; echo ${ss_espd}); fi
    mm_espd=$((10#${mm_stop}-10#${mm_strt})); if [ "${#mm_espd}" -le "1" ]; then mm_espd=$(for((i=1;i<=$((${#mm_stop}-${#mm_espd}));i++)); do echo -n "0"; done; echo ${mm_espd}); fi
    hh_espd=$((10#${hh_stop}-10#${hh_strt})); if [ "${#hh_espd}" -le "1" ]; then hh_espd=$(for((i=1;i<=$((${#hh_stop}-${#hh_espd}));i++)); do echo -n "0"; done; echo ${hh_espd}); fi
    dd_espd=$((10#${dd_stop}-10#${dd_strt})); if [ "${#dd_espd}" -le "1" ]; then dd_espd=$(for((i=1;i<=$((${#dd_stop}-${#dd_espd}));i++)); do echo -n "0"; done; echo ${dd_espd}); fi
    mh_espd=$((10#${mh_stop}-10#${mh_strt})); if [ "${#mh_espd}" -le "1" ]; then mh_espd=$(for((i=1;i<=$((${#mh_stop}-${#mh_espd}));i++)); do echo -n "0"; done; echo ${mh_espd}); fi
    yy_espd=$((10#${yy_stop}-10#${yy_strt})); if [ "${#yy_espd}" -le "1" ]; then yy_espd=$(for((i=1;i<=$((${#yy_stop}-${#yy_espd}));i++)); do echo -n "0"; done; echo ${yy_espd}); fi

    echo -e "${yy_espd}-${mh_espd}-${dd_espd} ${hh_espd}:${mm_espd}:${ss_espd}"
    #return $(echo -e "${yy_espd}-${mh_espd}-${dd_espd} ${hh_espd}:${mm_espd}:${ss_espd}")
    }

    mh_days(){
    mh_stop=$1; yy_stop=$2; #also checks if it's leap year or not

    case $mh_stop in
     [1,3,5,7,8,10,12]) mh_stop=31
     ;;
     2) (( !(yy_stop % 4) && (yy_stop % 100 || !(yy_stop % 400) ) )) && mh_stop=29 || mh_stop=28
     ;;
     [4,6,9,11]) mh_stop=30
     ;;
    esac

    return ${mh_stop}
    }

    appstart=$(date +%Y%m%d%H%M%S); read -p "Wait some time, then press nay-key..." key; appstop=$(date +%Y%m%d%H%M%S); elapsed=$(time_elapsed $appstop $appstart); echo -e "Start...: ${appstart:0:4}-${appstart:4:2}-${appstart:6:2} ${appstart:8:2}:${appstart:10:2}:${appstart:12:2}\nStop....: ${appstop:0:4}-${appstop:4:2}-${appstop:6:2} ${appstop:8:2}:${appstop:10:2}:${appstop:12:2}\n$(printf '%0.1s' "="{1..30})\nElapsed.: ${elapsed}"

    exit 0


-------------------------------------------- return
Wait some time, then press nay-key...
Start...: 2017-11-09 03:22:17
Stop....: 2017-11-09 03:22:18
==============================
Elapsed.: 0000-00-00 00:00:01
Rafał Białas
fuente