352

I would like to display the completion time of a script.

What I currently do is -

#!/bin/bash
date  ## echo the date at start
# the script contents
date  ## echo the date at end

This just show's the time of start and end of the script. Would it be possible to display a fine grained output like processor time/ io time , etc?

15 Answers 15

458

Just use time when you call the script:

time yourscript.sh
  • 61
    This outputs three times: real, user and sys. For the meanings of these, see here. – Garrett Jul 9 '15 at 1:56
  • 28
    and "real" is probably what people want to know - "Real is wall clock time - time from start to finish of the call" – Brad Parks Mar 29 '16 at 12:19
  • 3
    Is there a way to capture the stdout into a file? For example, something like time $( ... ) >> out.txt – Jon Apr 25 '18 at 18:18
  • 1
    You can also do this to sequences of commands on the command line, eg: time (command1 && command2) – meetar Apr 25 '18 at 20:30
  • 1
    Or he could, in his script, just do: echo $SECONDS assuming it is bash.... – Scott Prive Feb 22 at 17:10
177

If time isn't an option,

start=`date +%s`
stuff
end=`date +%s`

runtime=$((end-start))
  • 32
    Note that this only works if you don't need sub-second precision. For some uses that might be acceptable, for others not. For slightly better precision (you're still invoking date twice, for example, so you might at best get millisecond precision in practice, and probably less), try using date +%s.%N. (%N is nanoseconds since the whole second.) – a CVn Oct 19 '12 at 19:17
  • Good point. I thought of that just after leaving the keyboard but didn't come back. ^^ Also remember, OP, that "date" will itself add a few milliseconds to the run time. – Rob Bos Oct 20 '12 at 15:54
  • 2
    @ChrisH Oh. Good pointing it out; bash arithmetic expansion is integer-only. I see two obvious options; either ditch the period in the date format string (and treat the resultant value as nanoseconds since epoch), so use date +%s%N, or use something more capable like bc to calculate the actual runtime from the two values like jwchew suggests. Still, I feel this approach is a suboptimal way of doing it; time is considerably better if available, for reasons outlined above. – a CVn Jan 23 '14 at 13:29
  • 10
    Just install bc and then do this: runtime=$( echo "$end - $start" | bc -l ) – redolent Feb 11 '15 at 22:01
  • 10
    If your script takes several Minutes, use: echo "Duration: $((($(date +%s)-$start)/60)) minutes – rubo77 Jul 29 '16 at 4:42
75

Just call times without arguments upon exiting your script.

With ksh or zsh, you can also use time instead. With zsh, time will also give you the wall clock time in addition to the user and system CPU time.

To preserve the exit status of your script, you can make it:

ret=$?; times; exit "$ret"

Or you can also add a trap on EXIT:

trap times EXIT

That way, times will be called whenever the shell exits and the exit status will be preserved.

$ bash -c 'trap times EXIT; : {1..1000000}'
0m0.932s 0m0.028s
0m0.000s 0m0.000s
$ zsh -c 'trap time EXIT; : {1..1000000}'
shell  0.67s user 0.01s system 100% cpu 0.677 total
children  0.00s user 0.00s system 0% cpu 0.677 total

Also note that all of bash, ksh and zsh have a $SECONDS special variable that automatically gets incremented every second. In both zsh and ksh93, that variable can also be made floating point (with typeset -F SECONDS) to get more precision. This is only wall clock time, not CPU time.

  • 12
    That $SECONDS variable is very useful, thanks! – andybuckley Jun 18 '14 at 12:00
  • 1
    Does your approach eliminate the effect of temporal events? - - I think your approach is near time approach presented earlier. - - I think the timeit approach presented in MATLAB code can be useful here. – Léo Léopold Hertz 준영 Dec 25 '16 at 9:37
  • 2
    Hi, @Stéphane Chazelas. I found times doesn't give wall time, right? for example, bash -c 'trap times EXIT;sleep 2' – user15964 Jun 13 '17 at 2:17
  • Similar to unix.stackexchange.com/questions/269789/… – akhan Sep 10 '18 at 2:24
32

I'm a bit late to the bandwagon, but wanted to post my solution (for sub-second precision) in case others happen to stumble upon this thread through searching. The output is in format of days, hours, minutes, and finally seconds:

res1=$(date +%s.%N)

# do stuff in here

res2=$(date +%s.%N)
dt=$(echo "$res2 - $res1" | bc)
dd=$(echo "$dt/86400" | bc)
dt2=$(echo "$dt-86400*$dd" | bc)
dh=$(echo "$dt2/3600" | bc)
dt3=$(echo "$dt2-3600*$dh" | bc)
dm=$(echo "$dt3/60" | bc)
ds=$(echo "$dt3-60*$dm" | bc)

printf "Total runtime: %d:%02d:%02d:%02.4f\n" $dd $dh $dm $ds

Hope someone out there finds this useful!

  • 1
    I guess there is no other (only bash) way except using 'bc' to do the calculations. BTW really good script ;) – tvl Apr 18 '16 at 1:19
  • 1
    Beware that FreeBSD's date does not support sub-second precision and will just append literal “N” to the timestamp. – Anton Samsonov Jul 12 '16 at 9:25
  • 1
    Very nice script, but my bash doesn't handle the subsecond part. I also learned, that /1 in bc effectively strips that, so I added @/1@ to the $ds calculation, and it displays stuff very nice now! – Daniel Feb 1 '17 at 6:00
  • 1
    bc supports modulo: dt2=$(echo "$dt%86400" | bc). Just saying... BTW I prefer the form dt2=$(bc <<< "${dt}%86400") but that's entirely personal. – Dani_l Mar 7 '18 at 16:06
16

My method for bash:

# Reset BASH time counter
SECONDS=0
    # 
    # do stuff
    # 
ELAPSED="Elapsed: $(($SECONDS / 3600))hrs $((($SECONDS / 60) % 60))min $(($SECONDS % 60))sec"
  • This does show elapsed time, but it doesn't show "fine grained output like processor time, I/O time" as requested in the question. – roaima Jan 25 '17 at 22:10
  • 3
    Those searching for an answer to the question as posed are likely to find this solution useful. Your comment would be better addressed to the OPs question. – Mark Jan 26 '17 at 0:51
5
#!/bin/bash
start=$(date +%s.%N)

# HERE BE CODE

end=$(date +%s.%N)    
runtime=$(python -c "print(${end} - ${start})")

echo "Runtime was $runtime"

Yes, this calls Python, but if you can live with that then this is quite a nice, terse solution.

  • 5
    Beware that running that python command in a subshell and reading its output will take several millions of nanoseconds on most current systems. Same for running date. – Stéphane Chazelas Nov 22 '14 at 10:58
  • 7
    "Several millions of nanoseconds" is several milliseconds. People timing bash scripts are usually not very concerned about that (unless they run several billions of scripts per megasecond). – Zilk Jan 1 '16 at 3:34
  • 2
    Notice also that, even if the calculation is done inside a python process, the values were entirely collected before python was invoked. The execution time of the script will be measured correctly, without the "millions of nanoseconds" overhead. – Victor Schröder Feb 19 at 10:07
5

This question is quite old but in trying to find my favorite way of doing it this thread came up high... and I'm surprised no one mentioned it:

perf stat -r 10 -B sleep 1

'perf' is a performance analyzing tool included in the kernel under 'tools/perf' and often available to install as a separate package ('perf' in CentOS and 'linux-tools' on Debian/Ubuntu). The Linux Kernal perf Wiki has much more information about it.

Running 'perf stat' gives quite a bit of details including average execution time right at the end:

1.002248382 seconds time elapsed                   ( +-  0.01% )
  • 2
    What is perf? – B Layer Feb 20 '18 at 14:10
  • @B Layer - edited my answer to give a brief description of 'perf'. – Zaahid Feb 21 '18 at 15:42
  • 1
    perf stat now complains about "You may not have permission to collect stats." unless you run some commands with sudo, which makes it useless in all scenarios where you don't completely own target machine. – alamar Nov 22 '18 at 13:30
5

A small shell function that can be added before commands to measure their time:

tm() {
  local start=$(date +%s)
  $@
  local exit_code=$?
  echo >&2 "took ~$(($(date +%s)-${start})) seconds. exited with ${exit_code}"
  return $exit_code
}

Then use it in your script, or on your command line like so:

tm the_original_command with all its parameters
3

Here's a variation of Alex's answer. I only care about minutes and seconds, but I also wanted it formatted differently. So I did this:

start=$(date +%s)
end=$(date +%s)
runtime=$(python -c "print '%u:%02u' % ((${end} - ${start})/60, (${end} - ${start})%60)")
  • 1
    Why the downvote? Do I have a bug? – mpontillo Dec 25 '16 at 20:43
3
#!/bin/csh
#PBS -q glean
#PBS -l nodes=1:ppn=1
#PBS -l walltime=10:00:00
#PBS -o a.log
#PBS -e a.err
#PBS -V
#PBS -M shihcheng.guo@gmail.com
#PBS -m abe
#PBS -A k4zhang-group
START=$(date +%s)
for i in {1..1000000}
do
echo 1
done
END=$(date +%s)
DIFF=$(echo "$END - $START" | bc)
echo "It takes DIFF=$DIFF seconds to complete this task..."
  • 7
    How is this really different from the other answers already given which use date before and after the script and output the difference between them? – Eric Renouf Jan 15 '16 at 4:11
3

Using only bash it is also possible to measure and calculate the time duration for a portion of the shell script (or the elapsed time for the entire script):

start=$SECONDS

... # do time consuming stuff

end=$SECONDS

you can now either just print the difference:

echo "duration: $((end-start)) seconds."

if you only need incremental duration do:

echo "duration: $((SECONDS-start)) seconds elapsed.."

You can also store the duration in a variable:

let diff=end-start
  • ^ THIS is the answer folks. Or more simply: $SECONDS at the end. It's a BUILT-IN Bash variable. All of the other answers are just doing extra work to re-invent this wheel... – Scott Prive Feb 22 at 17:08
3

Personally, I like to wrap all my script code in some "main" function like so:

main () {
 echo running ...
}

# stuff ...

# calling the function at the very end of the script
time main

Notice how easy is to use the time command in this scenario. Obviously you're not measuring the precise time including script parse time, but I find it accurate enough in most situations.

1
#!/bin/bash
begin=$(date +"%s")

Script

termin=$(date +"%s")
difftimelps=$(($termin-$begin))
echo "$(($difftimelps / 60)) minutes and $(($difftimelps % 60)) seconds elapsed for Script Execution."
1

Timing function based on SECONDS, limited to second-level granularity only, doesn't use any external commands:

time_it() {
  local start=$SECONDS ts ec
  printf -v ts '%(%Y-%m-%d_%H:%M:%S)T' -1
  printf '%s\n' "$ts Starting $*"
  "$@"; ec=$?
  printf -v ts '%(%Y-%m-%d_%H:%M:%S)T' -1
  printf '%s\n' "$ts Finished $*; elapsed = $((SECONDS-start)) seconds"
  # make sure to return the exit code of the command so that the caller can use it
  return "$ec"
}

For example:

time_it sleep 5

gives

2019-03-30_17:24:37 Starting sleep 5 2019-03-30_17:24:42 Finished
sleep 5; elapsed = 5 seconds
0

For bash, use the internal $SECONDS variable. Here's a demo:

Note that dt_min gets rounded from 0.01666666666... (1 second = that many minutes) to 0.017 in this case since I'm using the printf function to round. The sleep 1; part below is where you'd call your script to run and time, but I'm just sleeping for 1 second instead for the sake of this demo:

start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
dt_sec = 1; dt_min = 0.017

Related:

protected by Community Mar 14 at 19:26

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.