How to measure time of program execution and store that inside a variable

In order to find out how long certain operations within a Bash (v4+) script take, I would like to parse the output from the time command “separately” and (ultimately) capture it within a Bash variable (let VARNAME=...).

Now, I am using time -f '%e' ... (or rather command time -f '%e' ... because of the Bash built-in), but since I already redirect the output of the executed command I’m really lost as to how I would go about to capture the output of the time command. Basically the problem here is to separate the output of time from the output of the executed command(s).

What I want is the functionality of counting the amount of time in seconds (integers) between starting a command and its completion. It doesn’t have to be the time command or the respective built-in.


Edit: given the two useful answers below, I wanted to add two clarifications.

  1. I do not want to throw away the output of the executed command, but it will not really matter whether it ends up on stdout or stderr.
  2. I would prefer a direct approach over an indirect one (i.e. catching output directly as opposed to store it in intermediate files).

The solution using date so far comes closes to what I want.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

To get the output of time into a var use the following:

<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ee9b9d9cae9d9c98">[email protected]</a> $ mytime="$(time ( ls ) 2>&1 1>/dev/null )"
<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2a5f59586a59585c">[email protected]</a> $ echo "$mytime"

real    0m0.006s
user    0m0.001s
sys     0m0.005s

You can also just ask for a single time type, e.g. utime:

<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dfaaacad9facada9">[email protected]</a> $ utime="$( TIMEFORMAT='%lU';time ( ls ) 2>&1 1>/dev/null )"
<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="6712141527141511">[email protected]</a> $ echo "$utime"
0m0.000s

To get the time you can also use date +%s.%N, so take it before and after execution and calculate the diff:

START=$(date +%s.%N)
command
END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)
# echo $DIFF

Method 2

In bash, the output of the time construct goes to its standard error, and you can redirect the standard error of the pipeline it affects. So let’s start with a command that writes to its output and error streamas: sh -c 'echo out; echo 1>&2 err'. In order not to mix up the command’s error stream with the output from time, we can temporarily divert the command’s error stream to a different file descriptor:

{ time -p sh -c 'echo out; echo 1>&2 err' 2>&3; }

This writes out to fd 1, err to fd 3, and the times to fd 2:

{ time -p sh -c 'echo out; echo 1>&2 err' 2>&3; } 
    3> >(sed 's/^/ERR:/') 2> >(sed 's/^/TIME:/') > >(sed 's/^/OUT:/')

It would be more pleasant to have err on fd 2 and the times on fd 3, so we swap them, which is cumbersome because there’s no direct way to swap two file descriptors:

{ { { time -p sh -c 'echo out; echo 1>&2 err' 2>&3; } 3>&2 2>&4; } 4>&3; } 3> >(sed 's/^/TIME:/') 2> >(sed 's/^/ERR:/') > >(sed 's/^/OUT:/')

This shows how you can postprocess the output of the command, but if you want to capture both the output of the command and its times, you need to work harder. Using a temporary file is one solution. In fact, it’s the only reliable solution if you need to capture both the command’s standard error and its standard output. But otherwise, you can capture the whole output and take advantage of the fact that time has a predictable format (if you use time -p to get the POSIX format or the bash-specific TIMEFORMAT variable).

nl=$'n'
output=$(TIMEFORMAT='%R %U %S %P'; mycommand)
set ${output##*$nl}; real_time=$1 user_time=$2 system_time=$3 cpu_percent=$4
output=${output%$nl*}

If you only care about wall clock time, running date before and after is a simple solution (if slightly more imprecise due to the extra time spent loading the external command).

Method 3

If you are in bash (and not sh) and you DO NOT need sub-second accuracy, you can skip the call to date entirely and do it without spawning any extra processes, without having to separate the combined output, and without having to capture and parse output from any commands:

# SECONDS is a bash special variable that returns the seconds since set.
SECONDS=0
mycmd <infile >outfile 2>errfile
DURATION_IN_SECONDS=$SECONDS
# Now `$DURATION_IN_SECONDS` is the number of seconds.

Method 4

With time, the command output comes out on stdout and the time comes out on stderr. So, to separate them, you can do:

command time -f '%e' [command] 1>[command output file] 2>[time output file]

But, now the time is in a file. I don’t think Bash is able to put stderr in a variable directly. If you don’t mind redirecting the command’s output somewhere, you can do:

FOO=$((( command time -f '%e' [command]; ) 1>outputfile; ) 2>&1; )

When you do this, the command’s output will be in outputfile and the time it took to run will be in $FOO.

Method 5

In Putting all together previous responses, when in OSX

ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G31+

you can do like

microtime() {
    python -c 'import time; print time.time()'
}
compute() {
    local START=$(microtime)
    #$1 is command $2 are args
    local END=$(microtime)
    DIFF=$(echo "$END - $START" | bc)
    echo "$1t$2t$DIFF"
}

Method 6

For that purpose it’s probably better to use times (than time) in bash which prints the accumulated user and system times for the shell and for processes run from the shell, example use:

$ (sleep 2; times) | (read tuser tsys; echo $tuser:$tsys)
0m0.001s:0m0.003s

See: help -m times for more info.

Method 7

try this, it runs a simple command with arguments and puts the times $real $user $sys and preserves the exit code.

It also does not fork subshells or trample on any variables except real user sys, and does not otherwise interfere with the running of the script

timer () {
  { time { "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="183c58">[email protected]</a>" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $?
  read -d "" _ real _ user _ sys _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1
}

e.g.

  timer find /bin /sbin /usr rm /tmp/
  echo $real $user $sys

note: it only times the simple command not a pipeline, whose components are run in a subshell

This version allows you to specify as $1 the name of the variables that should receive the 3 times:

timer () {
  { time { "${@:4}" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $? "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4b6f0b">[email protected]</a>"
  read -d "" _ "$2" _ "$3" _ "$4" _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1
}

e.g.

  timer r u s find /bin /sbin /usr rm /tmp/
  echo $r $u $s

and may be useful if it ends up being called recursively, to avoid trampling on times; but then r u s etc should be declared local in their use.

http://blog.sam.liddicott.com/2016/01/timeing-bash-commands.html

Method 8

Install /bin/time (e.g. pacman -S time)

So instead of error when trying -f flag:

$ time -f %e sleep 0.5
bash: -f: command not found

real    0m0.001s
user    0m0.001s
sys     0m0.001s

You can actually use it:

$ /bin/time -f %e sleep 0.5
0.50

And get what you want – time in variable (example uses %e for real elapsed time, for other options check man time):

#!/bin/bash
tmpf="$(mktemp)"
/bin/time -f %e -o "$tmpf" sleep 0.5
variable="$(cat "$tmpf")"
rm "$tmpf"

echo "$variable"

Method 9

Just as a heads up when working with the above statements and especially taking Grzegorz’s answer in mind. I was surprised to see on my Ubuntu 16.04 Xenial system these two results:

$ which time
/usr/bin/time

$ time -f %e sleep 4
-f: command not found
real    0m0.071s
user    0m0.056s
sys     0m0.012s

$ /usr/bin/time -f %e sleep 4
4.00

I don’t have any aliases set and so I don’t know why this is happening.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x