2
$ time (exec -a foo echo hello)
hello

似乎stderr(在哪里time写入其输出)在某处泄漏;显然这不是我的本意。

我的问题可以笼统地表述为“当子shell 执行另一个程序时,为什么终端上没有写入标准错误流? ”。

几点注意事项:

  1. 我需要使用exec它的-a开关,它改变了进程的第零个参数。我希望有一个替代方法exec来做到这一点,但我不知道,现在这种行为让我很好奇。
  2. 当然,我需要一个子shell,因为我希望我的脚本继续。同样,任何替代方案都将受到欢迎。在子shell中甚至是exec一件好事吗?
  3. time'ing 一个 subshel​​l 通常工作正常,所以它确实与exec.

有人能指出我正确的方向吗?我不确定在任何参考资料中从哪里开始,exec描述非常简洁。

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

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

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

更新: 总而言之,我们在这里有四个很好的答案,都是不同的,并且可能缺少一些东西:

  1. 使用 bash 默认和time正常使用的实际文件系统链接(硬链接或符号链接)。归功于 hek2mgl。

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

  2. fork用于time使用 bash 和exec使用没有特殊语法的 bash 子shell。

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

  3. fork使用timebash 但exec使用了一个小包装器。

    time launch -a foo echo hello

  4. fork以及exec使用time具有特殊语法的 bash。归功于 sjnarv。

    time { (exec -a foo echo hello); }

认为解决方案 1 的影响较小,time因为计时器不必exec在“代理”程序中计算,但不是很实用(许多文件系统链接),在技术上也不理想。在所有其他情况下,我们实际上是exec两次:一次加载代理程序(2 和 4 的子shell,3 的包装器),一次加载实际程序。这意味着time将计算第二个exec。虽然它可能非常便宜,exec但实际上文件系统查找可能非常慢(特别是如果它通过 搜索PATH,无论是本身exec*p还是代理进程)。

因此,唯一干净的方法(就该问题的答案而言)是修补 bash 以修改其time关键字,以便它可以exec同时将第零个参数设置为非零值。它可能看起来像time -a foo echo hello

4

3 回答 3

1

时间基于wait系统调用。从time手册页

time 显示的大多数信息都来自 wait3(2) 系统调用。

这仅time在要执行的命令的父进程时才有效。但exec创造了一个全新的过程。

随着时间的推移fork()wait()我不会过多关注第零个论点exec(当然,什么是有用的)。只需创建一个符号链接,然后将其称为:

time link_name > your.file 2>&1 &
于 2013-08-20T12:12:52.407 回答
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
于 2013-08-20T23:34:28.143 回答
1

我不认为计时器的输出会消失。我认为它(计时器)在 exec 覆盖的子 shell 中运行。

这是一个不同的调用。也许这会产生您最初的预期:

$ time { (exec -a foo echo hello); }

这对我来说发出:

hello

real    0m0.002s
user    0m0.000s
sys     0m0.001s
于 2013-08-21T00:00:18.680 回答