77

I'd like to use the time command in a bash script to calculate the elapsed time of the script and write that to a log file. I only need the real time, not the user and sys. Also need it in a decent format. e.g 00:00:00:00 (not like the standard output). I appreciate any advice.

The expected format supposed to be 00:00:00.0000 (milliseconds) [hours]:[minutes]:[seconds].[milliseconds]

I've already 3 scripts. I saw an example like this:

{ time { # section code goes here } } 2> timing.log

But I only need the real time, not the user and sys. Also need it in a decent format. e.g 00:00:00:00 (not like the standard output).

In other words, I'd like to know how to turn the time output into something easier to process.

kenorb
  • 155,785
  • 88
  • 678
  • 743
John Doe
  • 9,843
  • 13
  • 42
  • 73
  • 1
    New [tag:bash] versions (>= 4.2) do offer a `printf` syntaxe for this: `printf -v TimeStamp "%(%s)T" -1` and `printf "%(%a %d %b %Y %T)T\n" $TimeStamp` values could be `-1`: now, `-2`: Start of bash session, `Integer`: Unix timestamp – F. Hauri - Give Up GitHub Oct 16 '13 at 06:10
  • 1
    For *elapsed time* (under Linux) in **nanoseconds**, I wrote a [function](http://www.f-hauri.ch/vrac/cours_truc-et-astuces_2012-04-03/elap.bash). You could find explanations [here](http://stackoverflow.com/a/14110859/1765658). – F. Hauri - Give Up GitHub Oct 16 '13 at 06:14

6 Answers6

91

You could use the date command to get the current time before and after performing the work to be timed and calculate the difference like this:

#!/bin/bash

# Get time as a UNIX timestamp (seconds elapsed since Jan 1, 1970 0:00 UTC)
T="$(date +%s)"

# Do some work here
sleep 2

T="$(($(date +%s)-T))"
echo "Time in seconds: ${T}"

printf "Pretty format: %02d:%02d:%02d:%02d\n" "$((T/86400))" "$((T/3600%24))" "$((T/60%60))" "$((T%60))""

Notes: $((...)) can be used for basic arithmetic in bash – caution: do not put spaces before a minus - as this might be interpreted as a command-line option.

See also: http://tldp.org/LDP/abs/html/arithexp.html

EDIT:
Additionally, you may want to take a look at sed to search and extract substrings from the output generated by time.

EDIT:

Example for timing with milliseconds (actually nanoseconds but truncated to milliseconds here). Your version of date has to support the %N format and bash should support large numbers.

# UNIX timestamp concatenated with nanoseconds
T="$(date +%s%N)"

# Do some work here
sleep 2

# Time interval in nanoseconds
T="$(($(date +%s%N)-T))"
# Seconds
S="$((T/1000000000))"
# Milliseconds
M="$((T/1000000))"

echo "Time in nanoseconds: ${T}"
printf "Pretty format: %02d:%02d:%02d:%02d.%03d\n" "$((S/86400))" "$((S/3600%24))" "$((S/60%60))" "$((S%60))" "${M}"

DISCLAIMER:
My original version said

M="$((T%1000000000/1000000))"

but this was edited out because it apparently did not work for some people whereas the new version reportedly did. I did not approve of this because I think that you have to use the remainder only but was outvoted.
Choose whatever fits you.

Arc
  • 11,143
  • 4
  • 52
  • 75
  • Seems incorrect: Time in seconds: 46 (OK), Pretty format: 00:00:00:46 (incorrect). Using time command: real 0m46.018s user 0m7.630s sys 0m1.190s – John Doe Sep 10 '10 at 11:19
  • Hm, I'm not quite sure what 00:00:00:00 is supposed to mean – I was guessing it meant [days]:[hours]:[minutes]:[seconds] ? That is what it shows, but you are free to adjust the code to calculate what you need. – Arc Sep 10 '10 at 12:02
  • excuses for the typo. Supposed to be 00:00:00.0000 (milliseconds) [hours]:[minutes]:[seconds].[milliseconds] – John Doe Sep 10 '10 at 12:23
  • @mmrobins, well, I don't know what was wrong with the line `M="$((T%1000000000T/1000000))"` that it was changed to `M="$((T/1000000))"` which in my opinion is wrong... without the modulo, you'd get the entire time in milliseconds, which is printed before the milliseconds. This does not make sense in my eyes, however the suggestion was apparently approved for some reasons unclear to me. – Arc Oct 14 '12 at 14:30
  • 1
    At least for me the problem was that it didn't work with the line using the modulo. I got the following error when run on multiple versions of bash on different flavors of Linux: bash: T%1000000000T: value too great for base (error token is "1000000000T"). – mmrobins Oct 16 '12 at 14:44
  • Sorry, should have read `M="$((T%1000000000/1000000))"` but I guess the problem is due to your version of bash not supporting large numbers. Nevertheless, not doing a modulo will yield a wrong value because you only want the milliseconds that cannot be expressed with a larger unit like seconds. – Arc Oct 18 '12 at 08:41
  • The to calculate M correctly without base error use `M="$(((T/1000000)%1000/10))"`. – BayssMekanique May 19 '14 at 15:05
  • 1
    The reason for the 'value too great for base error' is that numbers starting with 0 are interpreted as octal numbers, and T sometimes starts with 0 but is not a valid octal number. Specifying base 10 should avoid this problem by removing the leading 0: today="$(( 10#$today ))". See also http://fahdshariff.blogspot.nl/2012/08/bash-error-value-too-great-for-base.html – Mauritz Hansen May 21 '14 at 07:32
  • I don't see the reason for this warning: "do not put spaces before a minus - as this might be interpreted as a command-line option." – mkj Sep 23 '16 at 08:26
  • This is probably a more complicated approach than necessary in most conditions. – Chris Jan 12 '22 at 19:03
59

To use the Bash builtin time rather than /bin/time you can set this variable:

TIMEFORMAT='%3R'

which will output the real time that looks like this:

5.009

or

65.233

The number specifies the precision and can range from 0 to 3 (the default).

You can use:

TIMEFORMAT='%3lR'

to get output that looks like:

3m10.022s

The l (ell) gives a long format.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 1
    Just what i needed, thank you. Would anyone know where the parameters for TIMEFORMAT are documented? I have – Kevin Apr 28 '21 at 20:51
  • 3
    @Kevin see [`man bash`](https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-TIMEFORMAT). – dimo414 May 15 '21 at 22:18
54

From the man page for time:

  1. There may be a shell built-in called time, avoid this by specifying /usr/bin/time
  2. You can provide a format string and one of the format options is elapsed time - e.g. %E

    /usr/bin/time -f'%E' $CMD

Example:

$ /usr/bin/time -f'%E' ls /tmp/mako/
res.py  res.pyc
0:00.01
Elazar
  • 20,415
  • 4
  • 46
  • 67
Spaceghost
  • 6,835
  • 3
  • 28
  • 42
  • 7
    That's why the command: time -f'%E' was not working, but /usr/bin/time -f'%E' works fine. Thx – John Doe Sep 10 '10 at 16:10
  • 5
    On OS X, `time` does not have a -f flag. You can fix this by installing coreutils using [brew](http://brew.sh) (`brew install coreutils`) and then use `/usr/local/bin/gtime` instead. – CousinCocaine Oct 13 '14 at 06:46
32

Use the bash built-in variable SECONDS. Each time you reference the variable it will return the elapsed time since the script invocation.

Example:

echo "Start $SECONDS"
sleep 10
echo "Middle $SECONDS"
sleep 10
echo "End $SECONDS"

Output:

Start 0
Middle 10
End 20
Michael Mrozek
  • 169,610
  • 28
  • 168
  • 175
Ed Lucero
  • 321
  • 3
  • 2
3

Not quite sure what you are asking, have you tried:

time yourscript | tail -n1 >log

Edit: ok, so you know how to get the times out and you just want to change the format. It would help if you described what format you want, but here are some things to try:

time -p script

This changes the output to one time per line in seconds with decimals. You only want the real time, not the other two so to get the number of seconds use:

time -p script | tail -n 3 | head -n 1
Andrew
  • 2,943
  • 18
  • 23
  • I want to include it in my script. – John Doe Sep 10 '10 at 11:30
  • Yes, if that was unambiguous then I wouldn't pointed out that it needs clarification. Why not use two scripts, one that calls the other? – Andrew Sep 10 '10 at 11:50
  • Because i've already 3 scripts. I saw an example like this: { time { # section code goes here } } 2> timing.log But i only need the real time, not the user and sys. Also need it in a decent format. e.g 00:00:00:00 (not like the standard output). – John Doe Sep 10 '10 at 11:58
  • ok, so you already know how to time a section, so that was just unnecessary confusion in your question. Is what you really want to know how to turn the time output into something easier to process, like just the number of seconds? – Andrew Sep 10 '10 at 16:42
  • yes i'd like to know how to turn the time output into something easier to process. – John Doe Sep 11 '10 at 05:46
1

The accepted answer gives me this output

# bash date.sh
Time in seconds: 51
date.sh: line 12: unexpected EOF while looking for matching `"'
date.sh: line 21: syntax error: unexpected end of file

This is how I solved the issue

#!/bin/bash

date1=$(date --date 'now' +%s) #date since epoch in seconds at the start of script
somecommand
date2=$(date --date 'now' +%s) #date since epoch in seconds at the end of script
difference=$(echo "$((date2-$date1))") # difference between two values
date3=$(echo "scale=2 ; $difference/3600" | bc) # difference/3600 = seconds in hours
echo SCRIPT TOOK $date3 HRS TO COMPLETE # 3rd variable for a pretty output.
Hani Umer
  • 21
  • 4