我可以创建自己的输出流,与stdout和stderr不同吗?

时间:2014-10-02 01:37:25

标签: shell stdout stderr

unistd.h的UNIX手册页指出:

The following symbolic constants are defined for file streams:
STDIN_FILENO  File number of stdin.  It is 0.
STDOUT_FILENO File number of stdout. It is 1.
STDERR_FILENO File number of stderr. It is 2.

grepping我的所有头文件,我发现这是真的。

[/usr]grep -r "STDIN_FILENO" include
include/unistd.h:#define    STDIN_FILENO    0   /* Standard input.  */

[/usr] grep -r "STDOUT_FILENO" include 
include/unistd.h:#define    STDOUT_FILENO   1   /* Standard output.  */

[/usr]grep -r "STDERR_FILENO" include
include/boost/asio/detail/impl/handler_tracking.ipp:  ::write(STDERR_FILENO, line, length);
include/unistd.h:#define    STDERR_FILENO   2   /* Standard error output.  */

即使它们被定义,它们似乎永远不会被我的机器上的任何其他std头文件使用。我觉得很奇怪。也许0,1和2在别处使用而不是定义的宏。这些宏只是作为流的配置方式的参考而存在。?

无论如何,我们可以通过这样做捕获shell中的特定输出流:

./program 1> stdout.txt
./program 2> stderr.txt
./program > both.txt 2>&1

我想创建自己的输出流,并通过执行以下操作捕获它:

./program 3> mine.txt

我尝试搜索unistd.h以及<iostream>所包含的其他文件来查看std::coutstd::cerr的工作方式,但正如您可能想象的那样,我迷失了方向并感到困惑。

我对你能否做到这一点更感兴趣,而不是它是否是一个好主意。

2 个答案:

答案 0 :(得分:4)

当您致电open时,会返回一个号码。您将该号码传递给readwrite。但是,您可以运行如下命令:

mycommand 3 3>bloop.txt

在mycommand中,将argv [1]转换为数字,并将其传递给write。

答案 1 :(得分:3)

打开的文件描述符由子进程继承。操作系统负责将流程与三个标准流连接,但您可以自由地执行任意数量的open(),然后执行exec()(最好是在之前的fork()之后)。然后,孩子可以扫描打开的文件描述符列表(/proc/self/fd/)或以某种方式“知道”使用哪些描述符。

这是一个用C语言编写的小例子。

#include <errno.h>   /* errno                        */
#include <stdio.h>   /* atoi, fprintf                */
#include <stdlib.h>  /* EXIT_SUCCESS, EXIT_FAILURE   */
#include <string.h>  /* strerror, strlen             */
#include <unistd.h>  /* write                        */

static const char *const message = "hello, world\n";

int main(int argc, char * * argv)
{
  int fd;
  int i;
  for (i = 1; i < argc; ++i)
    {
      fd = atoi(argv[i]);
      if (write(fd, message, strlen(message)) < 0)
        {
          fprintf(stderr, "error: cannot write to fd %d: %s\n",
                  fd, strerror(errno));
          return EXIT_FAILURE;
        }
    }
  return EXIT_SUCCESS;
}

程序的调用者负责打开程序应该写入的任何文件描述符,并通过命令行参数告诉它。

要将连接到文件redir的打开文件描述符3传递给它,我们可以使用shell的exec实用程序打开文件描述符并执行子文件。

$ exec 3>redir ./a.out 3

这将在子进程退出后关闭当前shell,因此您可能希望在子shell中尝试它:

$ sh -c 'exec 3>redir ./a.out 3'

或者,或者,不要使用exec,而是提到重定向语法@ bmargulies。在这里,我们写入标准错误输出(2),以及文件描述符3和4,我们将3重定向到标准输出(1),4重定向到文件redir

$ ./a.out 2 3 4 3>&1 4>redir
hello, world
hello, world
$ cat redir
hello, world

这种文件描述符的继承在服务器进程中大量使用,这些服务器进程允许其(非特权)子进程使用文件描述符来记录文件,文件描述符以及chroot() jail之外的文件,TCP连接等等。

不幸的是,在exec()使用子进程之前忘记关闭文件描述符是一个常见的错误,可能与安全相关。有一个Valgrind模块可以检查这个。