Reputation: 7261
$ time (exec -a foo echo hello)
hello
It seems as though stderr
(where time
writes its output) leaks somewhere; obviously this is not what I intended.
My question could be phrased in generic terms as "why isn't the standard error stream written on the terminal when a subshell executes another program?".
A few notes:
exec
for its -a
switch, which changes the zeroth argument of the process. I would appreciate an alternative to exec
to do just this, but I don't know of any, and now this behavior got me curious.exec
in a subshell even a good thing to do?time
'ing a subshell in general works fine, so it really has to do with exec
.Could somebody point me in the right direction? I'm not sure where to begin in any of the reference materials, exec
descriptions are pretty terse.
Update: Actually, I was just "lucky" with time
here being the bash builtin. It doesn't parse at all with /usr/bin/time
or with any other process:
$ env (exec -a foo echo hello)
bash: syntax error near unexpected token `exec'
Actually this makes sense, we can't pass a subshell as an argument. Any idea how to do this any other way?
Update: To summarize, we have four good answers here, all different, and potentially something lacking:
Use actual filesystem links (hard or symbolic) that bash will use by default and time
normally. Credits to hek2mgl.
ln $(which echo) foo && time ./foo hello && rm foo
fork
for time
using bash and exec
using a bash subshell without special syntax.
time bash -c 'exec -a foo echo hello'
fork
for time
using bash but exec
using a tiny wrapper.
time launch -a foo echo hello
fork
and exec
for time
using bash with special syntax. Credits to sjnarv.
time { (exec -a foo echo hello); }
I think that solution 1 has the less impact on time
as the timer doesn't have to count the exec
in the "proxy" program, but isn't very practical (many filesystem links) nor technically ideal. In all other cases, we actually exec
two times: once to load the proxy program (subshell for 2 and 4, wrapper for 3), and once to load the actual program. This means that time
will count the second exec
. While it can be extremely cheap, exec
actually does filesystem lookups which can be pretty slow (especially if it searches through PATH
, either itself with exec*p
or if the proxy process does).
So, the only clean way (as far as what the answers of this question covered) would be to patch bash to modify its time
keyword so that it can exec
while setting the zeroth argument to a non-zero value. It would probably look like time -a foo echo hello
.
Upvotes: 2
Views: 2539
Reputation: 2384
I don't think that the timer's output disappears. I think it (the timer) was running in the sub-shell overlaid by the exec.
Here's a different invocation. Perhaps this produces what you expected initially:
$ time { (exec -a foo echo hello); }
Which for me emits:
hello
real 0m0.002s
user 0m0.000s
sys 0m0.001s
Upvotes: 2
Reputation: 7261
So, I ended up writing that tiny C wrapper, which I call launch
:
#include <stdlib.h>
#include <unistd.h>
int main(const int argc, char *argv[])
{
int opt;
char *zeroth = NULL;
while ((opt = getopt(argc, argv, "a:")) != -1)
if (opt == 'a')
zeroth = optarg;
else
abort();
if (optind >= argc) abort();
argv += optind;
const char *const program = *argv;
if (zeroth) *argv = zeroth;
return execvp(program, argv);
}
I obviously simplified it to emphasize only what's essential. It essentially works just like exec -a
, except that since it is not a builtin, the shell will fork normally to run the launch
program as a separate process. There is thus no issue with time
.
The test
program in the following sample output is a simple program that only outputs its argument vector, one argument per line.
$ ./launch ./test hello world
./test
hello
world
$ ./launch -a foo ./test hello world
foo
hello
world
$ time ./launch -a foo ./test hello world
foo
hello
world
real 0m0.004s
user 0m0.001s
sys 0m0.002s
$ ./launch -a foo -- ./test -g hello -t world
foo
-g
hello
-t
world
The overhead should be minimal: just what's necessary to load the program, parse its single and optional argument, and manipulate the argument vector (which can be mostly reused for the next execvp
call).
The only issue is that I don't know of a good way to signal that the wrapper failed (as opposed to the wrapped program) to the caller, which may happen if it was invoked with erroneous arguments. Since the caller probably expects the status code from the wrapped program and since there is no way to reliably reserve a few codes for the wrapper, I use abort
which is a bit more rare, but it doesn't feel appropriate (nor does it make it all OK, the wrapped program may still abort itself, making it harder for the caller to diagnose what went wrong). But I digress, that's probably not interesting for the scope of this question.
Edit: just in case, the C compiler flags and feature test macros (gcc/glibc):
CFLAGS=-std=c11 -pedantic -Wall -D_XOPEN_SOURCE=700
Upvotes: 1
Reputation: 158100
Time is based on the wait
system call. From the time
man page
Most information shown by time is derived from the wait3(2) system call.
This will only work if time
is the father process of the command to be executed. But exec
creates a completely new process.
As time requires fork()
and wait()
I would not attach too much attention on that zeroth argument of exec
(what is useful, of course). Just create a symbolic link and then call it like:
time link_name > your.file 2>&1 &
Upvotes: 1