Michael Mao
Michael Mao

Reputation: 10168

Bash script to calculate time elapsed

I am writing a script in bash to calculate the time elapsed for the execution of my commands, consider:

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

I guess my logic is correct however I end up with the following print out:

"It takes seconds to complete this task..."

Anything wrong with my string evaluation?

I believe bash variables are untyped, I would love if there is a "string to integer" method in bash nevertheless.

Upvotes: 165

Views: 166181

Answers (12)

zappee
zappee

Reputation: 22746

This is an old post and it seems that everybody like writing too much code here. But actually, you only need 3 lines of simple bash code in order to you can show properly the elapsed time in a well-formatted way.

START_TIME=$(date +%s)

# put your code here

ELAPSED=$(($(date +%s) - START_TIME))
printf "elapsed: %s\n\n" "$(date -d@$ELAPSED -u +%H\ hours\ %M\ min\ %S\ sec)"

This produces the following result:

elapsed: 0 hours 2 min 19 sec

Test the code:

START_TIME=$(date +%s)
ELAPSED=86399
printf "elapsed: %s\n\n" "$(date -d@$ELAPSED -u +%H\ hour\ %M\ min\ %S\ sec)"

elapsed: 23 hour 59 min 59 sec

If you would like to show the days as well, then you need to do a little trick in order for the day will be displayed properly:

 ELAPSED=86399
 printf "elapsed: %s\n\n" "$(date -d@$ELAPSED -u +%d\ days\ %H\ hour\ %M\ min\ %S\ sec)"
 elapsed: 01 days 23 hour 59 min 59 sec

 ELAPSED=86400
 elapsed: 02 days 00 hour 00 min 00 sec

As you can see the day is calculated on a wrong way, so you need to do a math operation in bash this way:

printf "elapsed: %s day %s\n\n" "$(($(date -d@$ELAPSED -u +%d)-1))" "$(date -d@$ELAPSED -u +%H\ hour\ %M\ min\ %S\ sec)"
elapsed: 1 day 00 hour 00 min 00 sec

The \ character in the date-time pattern escapes the whitespace.

I hope that it helps you.

Upvotes: 6

perreal
perreal

Reputation: 98108

You are trying to execute the number in the ENDTIME as a command. You should also see an error like 1370306857: command not found. Instead use the arithmetic expansion:

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

You could also save the commands in a separate script, commands.sh, and use time command:

time commands.sh

Upvotes: 65

OmnipotentEntity
OmnipotentEntity

Reputation: 17131

Either $(()) or $[] will work for computing the result of an arithmetic operation. You're using $() which is simply taking the string and evaluating it as a command. It's a bit of a subtle distinction.

As tink pointed out in the comments on this answer, $[] is deprecated, and $(()) should be favored.

Upvotes: 97

Anton Samokat
Anton Samokat

Reputation: 1146

Combining internal variable "$SECONDS" in Lon Kaut's answer with ssc's one-liner under Internal Server Error's answer:

SECONDS=0
sleep 2s
echo "Elapsed time: $((SECONDS/3600))h $(((SECONDS/60)%60))m $((SECONDS%60))s"

($/${} is unnecessary on arithmetic variables)

Upvotes: 1

Internal Server Error
Internal Server Error

Reputation: 545

This is a one-liner alternative to Mike Q's function:

secs_to_human() {
    echo "$(( ${1} / 3600 ))h $(( (${1} / 60) % 60 ))m $(( ${1} % 60 ))s"
}

Upvotes: 10

Mike Q
Mike Q

Reputation: 7337

For larger numbers we may want to print in a more readable format. The example below does same as other but also prints in "human" format:

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

Simple testing:

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

Output:

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.

To use in a script as described in other posts (capture start point then call the function with the finish time:

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

Upvotes: 20

Rafał Białas
Rafał Białas

Reputation: 31

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

Upvotes: -4

Rafał Białas
Rafał Białas

Reputation: 31

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

Upvotes: -1

Lon Kaut
Lon Kaut

Reputation: 2656

I find it very clean to use the internal variable "$SECONDS"

SECONDS=0 ; sleep 10 ; echo $SECONDS

Upvotes: 230

Bulmaro Herrera
Bulmaro Herrera

Reputation: 121

Try the following code:

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

Upvotes: 12

gniourf_gniourf
gniourf_gniourf

Reputation: 46893

You can use Bash's time keyword here with an appropriate format string

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

Here's what the reference says about TIMEFORMAT:

The value of this parameter is used as a format string specifying how the timing information for pipelines prefixed with the time reserved word should be displayed. The ‘%’ character introduces an escape sequence that is expanded to a time value or other information. The escape sequences and their meanings are as follows; the braces denote optional portions.

%%

    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. 

The optional p is a digit specifying the precision, the number of fractional digits after a decimal point. A value of 0 causes no decimal point or fraction to be output. At most three places after the decimal point may be specified; values of p greater than 3 are changed to 3. If p is not specified, the value 3 is used.

The optional l specifies a longer format, including minutes, of the form MMmSS.FFs. The value of p determines whether or not the fraction is included.

If this variable is not set, Bash acts as if it had the value

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

If the value is null, no timing information is displayed. A trailing newline is added when the format string is displayed.

Upvotes: 36

Lynch
Lynch

Reputation: 9474

try using time with the elapsed seconds option:

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

or \time -f%e sleep 1 in interactive bash.

see the time man page:

Users of the bash shell need to use an explicit path in order to run the external time command and not the shell builtin variant. On system where time is installed in /usr/bin, the first example would become /usr/bin/time wc /etc/hosts

and

FORMATTING THE OUTPUT
...
    %      A literal '%'.
    e      Elapsed  real  (wall  clock) time used by the process, in
                 seconds.

Upvotes: 3

Related Questions