$ time (exec -a foo echo hello)
hello
似乎stderr
(其中time
写入其输出)泄漏到某处;显然这不是我想要的。
我的问题可以用通用术语表示为“当子shell执行另一个程序时,为什么不是在终端上写的标准错误流?”。
一些注意事项:
exec
作为其-a
开关,这会更改进程的第0个参数。我会很感激exec
的另一种选择,但我不知道,现在这种行为让我很好奇。exec
在子shell中是否也是一件好事?time
一般来说,子shell工作正常,所以它与exec
实际上有关。有人能指出我正确的方向吗?我不确定从哪个参考资料开始,exec
描述非常简洁。
更新:实际上,我只是“幸运”,time
这里是bash内置的。它不会使用/usr/bin/time
或任何其他进程进行解析:
$ env (exec -a foo echo hello)
bash: syntax error near unexpected token `exec'
实际上这是有道理的,我们不能将子shell作为参数传递。知道如何以其他方式做到这一点吗?
更新总而言之,我们在这里有四个好的答案,各种不同,可能还有一些缺点:
使用bash默认使用的实际文件系统链接(硬或符号),并且time
正常使用。积分为hek2mgl。
ln $(which echo) foo && time ./foo hello && rm foo
fork
time
使用bash而exec
使用bash子shell而无需特殊语法。
time bash -c 'exec -a foo echo hello'
fork
time
使用bash,exec
使用小包装。
time launch -a foo echo hello
fork
和exec
time
使用具有特殊语法的bash。致sjnarv。
time { (exec -a foo echo hello); }
我认为解决方案1对time
的影响较小,因为计时器不必计算“代理”程序中的exec
,但不是'非常实用(许多文件系统链接)也不是技术上理想的。在所有其他情况下,我们实际上exec
两次:一次加载代理程序(子shell为2和4,包装为3),一次加载实际程序。这意味着time
将计算第二个exec
。虽然它可以非常便宜,exec
实际上会进行文件系统查找,这可能非常慢(特别是如果它通过PATH
搜索,或者是exec*p
或者代理进程的搜索)。 / p>
因此,唯一干净的方法(就这个问题的答案所涵盖的那样)是修补bash以修改其time
关键字,以便它可以exec
同时将第0个参数设置为非零值。它可能看起来像time -a foo echo hello
。
答案 0 :(得分:1)
时间基于wait
系统调用。来自time
手册页
时间显示的大多数信息都来自wait3(2)系统调用。
仅当time
是要执行的命令的父进程时,这才有效。但exec
创造了一个全新的过程。
由于时间需要fork()
和wait()
,我不会过分关注exec
的第0个论点(当然有用)。只需创建一个符号链接,然后将其命名为:
time link_name > your.file 2>&1 &
答案 1 :(得分:1)
所以,我最后写了那个小C包装器,我称之为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);
}
我显然简化了它,只强调了必要的东西。它本质上就像exec -a
一样,除了因为它不是内置函数,shell将正常分叉以将launch
程序作为一个单独的进程运行。因此time
没有问题。
以下示例输出中的test
程序是一个简单的程序,只输出其参数向量,每行一个参数。
$ ./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
开销应该是最小的:只需要加载程序,解析其单个和可选参数,并操纵参数向量(可以在下一次execvp
调用中重复使用)。
唯一的问题是我不知道一个好方法来表示包装器失败(而不是包装程序)给调用者,如果用错误的参数调用它可能会发生。由于调用者可能需要来自包装程序的状态代码,并且因为无法为包装器可靠地保留一些代码,所以我使用abort
这有点罕见,但感觉不合适(它也没有一切正常,包装的程序可能仍会中止,使得调用者更难以诊断出错的原因。但是我离题了,这可能对这个问题的范围没有意义。
编辑:以防万一,C编译器标记和功能测试宏(gcc / glibc):
CFLAGS=-std=c11 -pedantic -Wall -D_XOPEN_SOURCE=700
答案 2 :(得分:1)
我不认为计时器的输出会消失。我认为它(计时器)正在运行 由exec覆盖的子shell。
这是一个不同的调用。也许这会产生你最初的预期:
$ time { (exec -a foo echo hello); }
对我而言:
hello
real 0m0.002s
user 0m0.000s
sys 0m0.001s