HiveBrain v1.2.0
Get Started
← Back to all entries
snippetbashMinor

Write the running time to file without capturing the program's output

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withoutthefilecapturingprogramoutputtimewriterunning

Problem

I want to run an application and write (append) its processing time into a file. Also, I want to see the stdout and stderr output on the shell, but don't want to write it in a file. After running the process, I'd like to see time measured.

Since I'm on Bash, one solution would be this one, but it's pretty ugly, since tail needs to parse the whole output:

function run_time {
    { local TIMEFORMAT=%R; time "${@}"; } 2>&1 | tee /dev/stderr | tail -n 1 >> run_time.txt
}


Another variant would be to use GNU time:

function run_time {
    /usr/bin/time --quiet --format '%e' --append --output=run_time.txt "${@}"
    tail -n 1 run_time.txt
}


When running the function like this, the measured time gets written into the file, of course.

run_time mycommand > target.txt


A work-around would to redirect tail's output to stderr or a sub-shell:

run_time sh -c "exec mycommand > target.txt"


And here's my question: It there a more elegant way?

Solution

Kudos

It's good to see appropriate quoting and correct use of "$@" in these shell scripts. Although the samples are short, it's still good to see nice readable code.

Problems with the first approach

  • All the output of the command is sent to /dev/stderr; it's not possible to pipe the output into another command, or to separate the output and error streams.



  • The exit status of the command is lost, so you can't use this function as part of a conditional (if, while, &&, ||, ...).



  • Only a single command can be timed, rather than a full pipeline.



Problems with the second approach

  • The exit status of the command is lost.



  • Only a single command can be timed (but that's always the case with an external time command, and why it's a built-in in Bash).



An improvement

You can retain the exit status when using GNU time, by using a process substitution as the output file:

function run_time {
     command time --quiet \
                  --format '%e' \
                  --output >(tee -a run_time.txt >&2) \
                  "${@}"
}


Notes

  • I've also used the command keyword to find the external time without hard-coding its path. You may or may not choose to follow this course.



  • I've sent the time result to standard error, for consistency with the command and built-in (note that the built-in outputs to the shell's standard error, not that of the pipeline it's timing).



Alternative using built-in

function run_time {
  local TIMEFORMAT='%R'
  { { time "$@" 2>&3; } 2> >(tee -a run_time.txt >&2); } 3>&2
}


That's a bit hard to follow, so let's unpick it from the inside:

-
{ time "$@" 2>&3; }


The command's standard output is unchanged (stream 1), but its stderr is redirected to stream 3; time's output will be on stream 2.

-
{...} 2> >(tee ... >&2)


The time output is redirected to the tee command; it writes to the file and to the enclosing stream 2 (the function's standard error stream).

-
{ ... } 3>2


The saved standard error stream of the timed command is diverted back to the function's standard error.

This allows independent redirection of the timed command's standard output and standard error (plus timing results). The function's error status is that of the timed command.

One disadvantage is that it doesn't wait for the process substitution to exit before the function returns, so tee's output may occur unexpectedly late (e.g. after Bash has shown the next command prompt, in an interactive shell).

Code Snippets

function run_time {
     command time --quiet \
                  --format '%e' \
                  --output >(tee -a run_time.txt >&2) \
                  "${@}"
}
function run_time {
  local TIMEFORMAT='%R'
  { { time "$@" 2>&3; } 2> >(tee -a run_time.txt >&2); } 3>&2
}
{ time "$@" 2>&3; }
{...} 2> >(tee ... >&2)
{ ... } 3>2

Context

StackExchange Code Review Q#127289, answer score: 2

Revisions (0)

No revisions yet.