Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

share|improve this question
    
The most direct way to get the data and handle it while still letting it run normally would be to do it in a C program using fork(), execvp() and wait3()/wait4(). This is ultimately what time and friends are doing. I'm not aware of a simle way to do it in bash/perl without redirecting to a file or similar approach. –  penguin359 Apr 26 '11 at 22:20
    
There is a related question that you might find interesting going on here. –  Caleb Apr 26 '11 at 23:09
    
@Caleb: thanks. Indeed interesting. However, for my purposes it's sufficient to do it in a script. –  0xC0000022L Apr 26 '11 at 23:28

3 Answers 3

up vote 20 down vote accepted

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

usr@srv $ mytime="$(time ( ls ) 2>&1 1>/dev/null )"
usr@srv $ echo "$mytime"

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

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

usr@srv $ utime="$( TIMEFORMAT='%lU';time ( ls ) 2>&1 1>/dev/null )"
usr@srv $ 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

Hope that helps.

share|improve this answer
3  
I didn't want to throw away the output from the command though. So I guess your third code block is closest to what I had in mind. Although I'd write the last one as DIFF=$((END-START)), making use of the arithmetic expressions. :) ... thanks for the answer. +1 –  0xC0000022L Apr 26 '11 at 20:00
    
@STATUS_ACCESS_DENIED: Bash doesn't do floating point arithmetic, so if you want better than second resolution (.%N in binfalse's code), you either need bc or fancier calculations. –  Gilles Apr 26 '11 at 21:11
    
@Gilles: I know. As I wrote in my question integers are fine. No need to have a higher resolution than second. But thanks, the date invocation would have to be changed. I had realized that, though. –  0xC0000022L Apr 26 '11 at 23:25

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).

share|improve this answer
    
Wow, a lot to test. Thanks a lot for the extensive reply. +1. –  0xC0000022L Apr 26 '11 at 23:25

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.

share|improve this answer
1  
ooh, I didn't realize. I knew time writes to stderr, but I didn't realize that it would merge stdout and stderr of the executed command into its stdout. But generally there is no direct method (i.e. without intermediate file)? +1. –  0xC0000022L Apr 26 '11 at 19:57
3  
@STATUS_ACCESS_DENIED: time does not merge its command's stdout and stderr. The way I showed assumed that you only needed the command's stdout output. Since bash will only store what comes out of stdout, you have to redirect. In case you can safely drop your command's stderr: the time will always be on the last line of stderr. If you do need both your command's output streams, I'd suggest wrapping it in another script. –  marinus Apr 26 '11 at 20:27

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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