【问题标题】:Can a file descriptor be duplicated multiple times?文件描述符可以重复多次吗?
【发布时间】:2019-09-24 10:26:48
【问题描述】:

我已经找了很长时间了,但找不到我的问题的答案。

我正在尝试在 C 中复制一个带有完整重定向的 shell。为此,我想在执行命令之前打开文件。

例如,在ls > file1 > file2 中,我使用dup2(file1_fd, 1)dup2(file2_fd, 1),然后执行ls 来填充文件,但似乎标准输出只能打开一次,所以只有file2填充,因为它是最后一个被复制的。

有没有办法将标准输出重定向到多个文件?

我有什么遗漏吗?谢谢!

【问题讨论】:

  • 只要您没有达到系统管理员对您的帐户施加的进程限制的次数。您不能忽略的打开文件的数量是有限制的。这个问题不是关于编程的,而是关于 Unix 内部工作的。尽管如此,我不会投票赞成关闭它。

标签: c unix system-calls file-descriptor


【解决方案1】:

有没有办法将标准输出重定向到多个文件?

许多文件描述符不能成为一个文件描述符。您需要分别写入每个文件描述符。这就是 tee 实用程序为您所做的。

【讨论】:

  • 谢谢!我一定会这样做的!
  • 但这并不意味着重定向。重定向是当您不知道该描述符来自何处时。重定向确实是一种约定,你总是会收到三个已经打开的描述符,但没有什么能阻止你接收 5 个(就像 ms-dos 所做的那样)或根本没有(只是在调用close(2) 之前close(2) 他们)tee(1) 需要一个文件名称来打开它并写入,因此它知道(并打开)它正在写入的文件。
【解决方案2】:

您要问的是tee 命令存在的确切原因(您可以查看其源代码here)。

您不能多次使用dup2() 复制文件描述符。正如您已经看到的,最后一个会覆盖任何先前的重复。因此,您不能直接使用dup2() 将程序的输出重定向到多个文件。

为此,您确实需要多个描述符,因此您必须打开这两个文件,使用popen() 启动命令,然后从管道读取并写入这两个文件。

这里有一个非常简单的例子来说明如何做到这一点:

#include <stdio.h>
#include <stdlib.h>

#define N 4096

int main(int argc, const char *argv[]) {
    FILE *fp1, *fp2, *pipe;

    fp1 = fopen("out1.txt", "w");
    if (fp1 == NULL) {
        perror("fopen out1 failed");
        return 1;
    }

    fp2 = fopen("out2.txt", "w");
    if (fp2 == NULL) {
        perror("fopen out2 failed");
        return 1;
    }

    // Run `ls -l` just as an example.
    pipe = popen("ls -l", "r");

    if (pipe == NULL) {
        perror("popen failed");
        return 1;
    }

    size_t nread, nwrote;
    char buf[N];

    while ((nread = fread(buf, 1, N, pipe))) {
        nwrote = 0;
        while (nwrote < nread)
            nwrote += fwrite(buf + nwrote, 1, nread - nwrote, fp1);

        nwrote = 0;
        while (nwrote < nread)
            nwrote += fwrite(buf + nwrote, 1, nread - nwrote, fp2);
    }

    pclose(pipe);
    fclose(fp2);
    fclose(fp1);

    return 0;
}

上面的代码只是粗略估计整个事情是如何工作的,它不会检查freadfwrite等的一些错误:你当然应该在你的最终程序中检查错误.

还很容易看出如何扩展它以支持任意数量的输出文件(只需使用 FILE * 的数组)。

【讨论】:

    【解决方案3】:

    标准输出与任何其他打开的文件没有什么不同,唯一的特殊特征是它是文件描述符1(因此您的进程中只能有一个索引为1的文件描述符)您可以dup(2)文件描述符1 获取,比如说文件描述符6。这就是dup() 的使命,只是为了获得另一个文件描述符(具有不同的编号)而不是您用作源的文件描述符,但对于相同的源。复制的描述符允许您无差别地使用任何复制的描述符来输出,或更改打开的标志,如 close on exec 标志或 非块append 标志(并非所有都是共享的,我不确定哪些可以更改而不会影响 dup 中的其他人)。它们共享文件指针,因此您尝试对任何文件描述符的每个write() 都会在其他文件描述符中更新。

    但重定向的想法并非如此。 Unix 中的一个约定是,每个程序都会从​​其父进程接收三个已经打开的描述符。所以要使用分叉,首先你需要考虑如何编写符号来表示一个程序将接收(已经打开)多个输出流(这样你就可以正确地重定向它们中的任何一个,在调用程序之前) 这同样适用于加入流。在这里,问题更加复杂,因为您需要表达如何将数据流合并为一个,这使得合并问题与问题相关。

    文件dup()ping 不是一种让文件描述符写入两个文件的方法……但反过来,它是一种让两个不同的文件描述符引用同一个文件的方法.

    做你想做的唯一方法是在你将要使用的每个文件描述符上复制write(2)调用。

    正如一些答案所评论的,tee(1) 命令允许您在管道中分叉数据流,但不能使用文件描述符,tee(1) 只是打开一个文件,write(2)s 所有输入都在除了 write(2) 也可以将其写入标准输出。

    没有规定在 shell 中分叉数据流,因为没有规定在输入时加入(并行)数据流。我认为这是 Steve Bourne 在外壳设计中放弃的一些想法,您可能会达到同样的观点。

    顺便说一句,只需研究使用通用 dup2() 运算符的可能性,即 n&gt;&amp;m>,但再次考虑一下,对于重定向程序,2&gt;&amp;3 2&gt;&amp;4 2&gt;&amp;5 2&gt;&amp;6 表示您已经预先打开了 7 个文件描述符,0...6 其中stderr 是描述符36 的别名(所以任何数据写入任何这些描述符中的一个将出现在stderr) 中,或者您可以使用2&lt;file_a 3&lt;file_b 4&lt;file_c,这意味着您的程序将使用从file_a 重定向的文件描述符2 (stderr) 和文件描述符34 执行已经从文件 file_bfile_c 打开。可能应该设计一些符号(现在我并不容易想到如何设计它)以允许在已启动以执行某些任务的不同进程之间进行管道(使用pipe(2) 系统调用),但是您需要构建一个通用图以实现通用性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-12
      • 1970-01-01
      • 1970-01-01
      • 2011-03-26
      • 2016-08-06
      • 1970-01-01
      相关资源
      最近更新 更多