1

I have the following command line:

time `while [ $i -lt 200000 ]; do i=$[$i+1]; done` | grep "real"

from what I know of bash this should first give me the run time of the while loop and use it as the input for the grep command and then the grep command should print out only the real time given by the time command but instead it prints the full output of the time command

so why is it not working as I would expect. also is there a better way to do this?

  • As an aside: Do not put the command to time in backticks. What makes time special (a shell keyword) is that it allows you to specify a command line to time _as is_ - even an _entire pipeline_. Indeed, that's the source of your problem: the `grep` command becomes part of what is being timed. – mklement0 Mar 02 '15 at 20:30

3 Answers3

3

The bash-builtin time command is a bit of a special case in bash. In fact, it's classified as a keyword (try running type time).

It prints its output on stderr, but through some magic bash sort of "raises" that output outside of its containing pipeline, so even if you pipe stderr from the command through a pipe, it won't get through.

What you need to do is surround the time command in a braced block (this causes the output of time to become part of the block's stderr stream), redirect the block's stderr through a pipe, and then you'll have the time output:

{ time while [ $i -lt 200000 ]; do i=$[$i+1]; done; } 2>&1| grep real;
bgoldst
  • 34,190
  • 6
  • 38
  • 64
2

You need to capture stderr from time:

$ i=0; { time while [ "$i" -lt 200000 ]; do i=$[$i+1]; done; } 2>&1 | grep "real"
real    0m2.799s

Discussion

The shell keyword time operates on a whole pipeline and reports the timing information on stderr. To capture that output, it is necessary to put time inside a group, {...;}, or subshell, (...), and then collect the stderr from that list or subshell.

Documentation

man bash explains the pipeline syntax as follows:

   Pipelines
       A  pipeline  is  a  sequence  of one or more commands separated by one of the control operators | or |&.  The format for a
       pipeline is:

              [time [-p]] [ ! ] command [ [|⎪|&] command2 ... ]

   ...

   If  the  time reserved word precedes a pipeline, the elapsed as well as user and system time consumed by its execution are
   reported when the pipeline terminates.
John1024
  • 109,961
  • 14
  • 137
  • 171
1

Really, your grep is not a good idea. Bash has a wonderful time keyword and you can format its output as you wish.

In your case, I'd just do this:

TIMEFORMAT=$'real\t%3lR'
i=0
time while [ "$i" -lt 200000 ]; do i=$[$i+1]; done

See the TIMEFORMAT specs in Bash Variables section.


Now, your command shows antique shell techniques. In modern Bash, your while loop would be written as:

while ((i<200000)); do ((++i)); done

About the time keyword, you can also have a look at this question: Parsing the output of Bash's time builtin.

Community
  • 1
  • 1
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
  • Nicely done; never knew about `TIMEFORMAT`; sneak preview of the manual (sans format modifiers): `%R` is the real time elapsed, `%U` is the time spent in user mode, `%S`, the time spent in system mode. `%P` reports the CPU percentage. Format modifiers: A number between 0 and 3 determines the decimal places, and `l` shows minutes and (fractional) seconds (instead of just seconds). Given that `%R` presumably includes time spent servicing _other_ processes, is there an easy way to get the total time spent on _just_ the bash command? – mklement0 Mar 02 '15 at 20:28