时间exec在子shell中

时间:2013-08-20 12:04:09

标签: bash stderr io-redirection

$ time (exec -a foo echo hello)
hello

似乎stderr(其中time写入其输出)泄漏到某处;显然这不是我想要的。

我的问题可以用通用术语表示为“当子shell执行另一个程序时,为什么不是在终端上写的标准错误流?”。

一些注意事项:

  1. 我需要使用exec作为其-a开关,这会更改进程的第0个参数。我会很感激exec的另一种选择,但我不知道,现在这种行为让我很好奇。
  2. 当然,我需要一个子shell,因为我希望我的脚本继续。同样,欢迎任何替代方案。 exec在子shell中是否也是一件好事?
  3. time一般来说,子shell工作正常,所以它与exec实际上有关。
  4. 有人能指出我正确的方向吗?我不确定从哪个参考资料开始,exec描述非常简洁。

    更新:实际上,我只是“幸运”,time这里是bash内置的。它不会使用/usr/bin/time或任何其他进程进行解析:

    $ env (exec -a foo echo hello)
    bash: syntax error near unexpected token `exec'
    

    实际上这是有道理的,我们不能将子shell作为参数传递。知道如何以其他方式做到这一点吗?

    更新总而言之,我们在这里有四个好的答案,各种不同,可能还有一些缺点:

    1. 使用bash默认使用的实际文件系统链接(硬或符号),并且time正常使用。积分为hek2mgl。

      ln $(which echo) foo && time ./foo hello && rm foo

    2. {li>

      fork time使用bash而exec使用bash子shell而无需特殊语法。

      time bash -c 'exec -a foo echo hello'

    3. fork time使用bash,exec使用小包装。

      time launch -a foo echo hello

    4. {li>

      forkexec 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

3 个答案:

答案 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