Issue
I time a command that has some output. I want to output the real time from the time command to a file, but leave the output of the command to the console.
For example if I do time my_command
I get printed in the console
several lines of output from my_command ...
real 1m25.970s
user 0m0.427s
sys 0m0.518s
In this case, I want to store only 1m25.970s
to a file, but still print to the console the output of the command.
Solution
The time
command is tricky. The POSIX specification of time
doesn't define the default output format, but does define a format for the -p
(presumably for 'POSIX') option. Note the (not easily understood) discussion of command sequences in pipelines.
The Bash specification say time
prefixes a 'pipeline', which means that time cmd1 | cmd2
times both cmd1
and cmd2
. It writes its results to standard error. The Korn shell is similar.
The POSIX format requires a single space between the tags such as real
and the time; the default format often uses a tab instead of a space. Note that the /usr/bin/time
command may have yet another output format. It does on macOS, for example, listing 3 times on a single line, by default, with the label after the time value; it supports -p
to print in an approximation to the POSIX format (but it has multiple spaces between label and time).
You can easily get all the information written to standard error into a file:
(time my_command) 2> log.file
If my_command
or any programs it invokes reports any errors to standard error, those will got to the log file too. And you will get all three lines of the output from time
written to the file.
If your shell is Bash, you may be able to use process substitution to filter some of the output.
I wouldn't try it with a single command line; the hieroglyphs needed to make it work are ghastly and best encapsulated in shell scripts.
For example, a shell script time.filter
to capture the output from time
and write only the real
time to a log file (default log.file
, configurable by providing an alternative log file name as the first argument
#!/bin/sh
output="${1:-log.file}"
shift
sed -E '/^real[[:space:]]+(([0-9]+m)?[0-9]+[.][0-9]+s?)/{ s//\1/; w '"$output"'
d;}
/^(user|sys)[[:space:]]+(([0-9]+m)?[0-9]+[.][0-9]+s?)/d' "$@"
This assumes your sed
uses -E
to enable extended regular expressions.
The first line of the script finds the line containing the real
label and the time after it (in a number of possible formats — but not all). It accepts an optional minutes value such as 60m05.003s
, or just a seconds value 5.00s
, or just 5.0
(POSIX formats — at least one digit after the decimal point is required). It captures the time part and prints it to the chosen file (by default, log.file
; you can specify an alternative name as the first argument on the command line). Note that even GNU sed
treats everything after the w
command as file name; you have to continue the d
(delete) command and the close brace }
on a newline. GNU sed
does not require the semicolon after d
; BSD (macOS) sed
does. The second line recognizes and deletes the lines reportin the user
and sys
times. Everything else is passed through unaltered.
The script processes any files you give it after the log file name, or standard input if you give it none. A better command line notation would use an explicit option (-l logfile
) and getopts
to specify the log file.
With that in place, we can devise a program that reports to standard error and standard output — my_command
:
echo "nonsense: error: positive numbers are required for argument 1" >&2
dribbler -s 0.4 -r 0.1 -i data -t
echo "apoplexy: unforeseen problems induced temporary amnesia" >&2
You could use cat data
instead of the dribbler
command. The dribbler
command as shown reads lines from data
, writes them to standard output, with a random delay with a gaussian distribution between lines. The mean delay is 0.4 seconds; the standard deviation is 0.1 seconds. The other two lines are pretending to be commands that report errors to standard error.
My data
file contained a nonsense 'poem' called 'The Great Panjandrum'.
With this background in place, we can run the command and capture the real time in log.file
, delete (ignore) the user and system time values, while sending the rest of standard error to standard error by using:
$ (time my_command) 2> >(tee raw.stderr | time.filter >&2)
nonsense: error: positive numbers are required for argument 1
So she went into the garden
to cut a cabbage-leaf
to make an apple-pie
and at the same time
a great she-bear coming down the street
pops its head into the shop
What no soap
So he died
and she very imprudently married the Barber
and there were present
the Picninnies
and the Joblillies
and the Garyulies
and the great Panjandrum himself
with the little round button at top
and they all fell to playing the game of catch-as-catch-can
till the gunpowder ran out at the heels of their boots
apoplexy: unforeseen problems induced temporary amnesia
$ cat log.file
0m7.278s
$
(The time taken is normally between 6 and 8 seconds. There are 17 lines, so you'd expect it to take around 6.8 seconds at 0.4 seconds per line.) The blank line is from time
; it is pretty hard to remove that blank line, and only that blank line, especially as POSIX says it is optional. It isn't worth it.
Answered By - Jonathan Leffler