BUG in the "time" Bash built-in?

Aug. 27, 2018, 12:55 a.m.

In Bash, time is a built-in reserved word you can prepend to a pipeline to measure the duration of executed commands. It implements a subset of features of the original time binary, but does not involve shelling-out. time built-in will output the measurements to STDERR. Under some circumstances (time is used to measure a failing function executed in a subshell with errexit enabled), the STDERR of time will leak into the executed command.

Example:

Let's define a couple of functions first

$ failing_fn () { false; }

$ successful_fn () { true; }

The following will result in measurements written into testfile (while it shouldn't, redirect happens in the subshell)

$  time (set -e; failing_fn 2>./testfile)
$  cat testfile

real    0m0.000s
user    0m0.000s
sys 0m0.000s

This does not happen if the subshell command is not a function

$  time (set -e; false 2>./testfile)

real    0m0.000s
user    0m0.000s
sys 0m0.000s
$  cat testfile

it also doesn't happen when the failing function does not fail the pipeline (errexit is not enabled)

$  time (set +e; failing_fn 2>./testfile)

real    0m0.000s
user    0m0.000s
sys 0m0.000s
$  cat testfile

or if the function is successful

$  time (set -e; successful_fn 2>./testfile)

real    0m0.000s
user    0m0.000s
sys 0m0.000s
$  cat testfile

or when we do not use time with the subshell syntax directly

$  time eval '(set -e; failing_fn 2>./testfile)'

real    0m0.001s
user    0m0.001s
sys 0m0.000s
$  cat testfile

Oddly enough, simply adding | cat to the pipeline inside the subshell will result in measurements output being duplicated into both STDERR of the current shell and STDERR of the subshell function

$  time (set -e; failing_fn 2>./testfile | cat)

real    0m0.001s
user    0m0.001s
sys 0m0.001s
$  cat testfile

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

and it works properly in case | cat and eval are present in the subshell (found this one by trial-and-error)

$  time (set -e; eval failing_fn 2>./testfile | cat)

real    0m0.001s
user    0m0.001s
sys 0m0.001s
$  cat testfile