一个文件描述符可以被复制多次吗?
Can a file descriptor be duplicated multiple times?
我找了好久都没找到问题的答案。
我正在尝试使用完全重定向在 C 中重现 shell。为此,我想在执行命令之前打开文件。
比如ls > file1 > file2
,我用dup2(file1_fd, 1)
和dup2(file2_fd, 1)
然后执行ls
来填充文件,但是好像标准输出只能打开一次,所以只有 file2
会被填充,因为它是最后一个被复制的。
有没有办法将标准输出重定向到多个文件?
有什么我想念的吗?谢谢!
Is there a way to redirect standard output to multiple files?
许多文件描述符不能成为一个文件描述符。您需要分别写入每个文件描述符。这就是 tee
实用程序为您所做的。
你问的是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;
}
上面的代码只是粗略估计整个事情是如何工作的,它没有检查 fread
、fwrite
等的一些错误:你当然应该检查对于最终程序中的错误。
也很容易看出如何扩展它以支持任意数量的输出文件(只需使用 FILE *
的数组)。
标准输出与任何其他打开的文件没有什么不同,唯一的特点是它是文件描述符1
(所以你的进程中只能有一个索引为1的文件描述符)你可以dup(2)
文件描述符 1
得到,比方说文件描述符 6
。这就是 dup()
的任务,只是为了获得另一个文件描述符(具有不同的编号)而不是您用作源的文件描述符,但对于相同的源。复制的描述符允许您无差别地使用任何复制的描述符来输出,或更改打开标志,如 close on exec 标志或 non block 或 append 标志(不是所有的都是共享的,我不确定哪些可以在不影响其他人的情况下更改)。它们共享文件指针,因此您每次 write()
尝试任何文件描述符都会在其他文件描述符中更新。
但是重定向的想法不是那样的。 unix 中的约定是每个程序将从其父进程接收三个描述符already open。所以要使用分叉,首先你需要考虑如何写符号来表达一个程序将接收(已经打开)多个输出流(这样你就可以正确地重定向其中的任何一个,在调用程序之前) 这同样适用于加入流。在这里,问题更复杂,因为您需要表达数据流如何合并为一个,这使得合并问题依赖于问题。
File dup()
ping 不是使文件描述符写入两个文件的方法...但相反,它是使 two 的方法引用同一个文件的不同文件描述符.
做你想做的事情的唯一方法是对你将要使用的每个文件描述符重复 write(2)
调用。
正如某些回答所评论的那样,tee(1)
命令允许您在管道中分叉数据流,但不能使用文件描述符,tee(1)
只是打开一个文件,而 write(2)
那里有所有的输入,除了 write(2)`ing 到 stdout 也。
shell 中没有分叉数据流的规定,因为没有规定在输入时加入(并行)数据流。我认为这是 Steve Bourne 的 shell 设计中一些被遗弃的想法,您可能会遇到同样的问题。
顺便说一句,研究一下使用通用dup2()
运算符的可能性,即<n>&
m>,但再次考虑,对于重定向程序,2>&3 2>&4 2>&5 2>&6
意味着您已经预先打开了 7 个文件描述符,0
...6
其中 stderr
是描述符 3
到 6
的别名(因此写入任何这些描述符的任何数据都会出现在 stderr
中)或者您可以使用 2<file_a 3<file_b 4<file_c
表示您的程序将使用从 file_a
重定向的文件描述符 2
(stderr) 执行,并且文件描述符 3
和 4
已经从文件打开 file_b
和 file_c
。可能,应该设计一些符号(现在在我看来并不容易,如何设计它)以允许在已启动的不同进程之间进行管道(使用 pipe(2)
系统调用)来做一些事情任务,但您需要构建一个通用图以实现通用性。
我找了好久都没找到问题的答案。
我正在尝试使用完全重定向在 C 中重现 shell。为此,我想在执行命令之前打开文件。
比如ls > file1 > file2
,我用dup2(file1_fd, 1)
和dup2(file2_fd, 1)
然后执行ls
来填充文件,但是好像标准输出只能打开一次,所以只有 file2
会被填充,因为它是最后一个被复制的。
有没有办法将标准输出重定向到多个文件?
有什么我想念的吗?谢谢!
Is there a way to redirect standard output to multiple files?
许多文件描述符不能成为一个文件描述符。您需要分别写入每个文件描述符。这就是 tee
实用程序为您所做的。
你问的是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;
}
上面的代码只是粗略估计整个事情是如何工作的,它没有检查 fread
、fwrite
等的一些错误:你当然应该检查对于最终程序中的错误。
也很容易看出如何扩展它以支持任意数量的输出文件(只需使用 FILE *
的数组)。
标准输出与任何其他打开的文件没有什么不同,唯一的特点是它是文件描述符1
(所以你的进程中只能有一个索引为1的文件描述符)你可以dup(2)
文件描述符 1
得到,比方说文件描述符 6
。这就是 dup()
的任务,只是为了获得另一个文件描述符(具有不同的编号)而不是您用作源的文件描述符,但对于相同的源。复制的描述符允许您无差别地使用任何复制的描述符来输出,或更改打开标志,如 close on exec 标志或 non block 或 append 标志(不是所有的都是共享的,我不确定哪些可以在不影响其他人的情况下更改)。它们共享文件指针,因此您每次 write()
尝试任何文件描述符都会在其他文件描述符中更新。
但是重定向的想法不是那样的。 unix 中的约定是每个程序将从其父进程接收三个描述符already open。所以要使用分叉,首先你需要考虑如何写符号来表达一个程序将接收(已经打开)多个输出流(这样你就可以正确地重定向其中的任何一个,在调用程序之前) 这同样适用于加入流。在这里,问题更复杂,因为您需要表达数据流如何合并为一个,这使得合并问题依赖于问题。
File dup()
ping 不是使文件描述符写入两个文件的方法...但相反,它是使 two 的方法引用同一个文件的不同文件描述符.
做你想做的事情的唯一方法是对你将要使用的每个文件描述符重复 write(2)
调用。
正如某些回答所评论的那样,tee(1)
命令允许您在管道中分叉数据流,但不能使用文件描述符,tee(1)
只是打开一个文件,而 write(2)
那里有所有的输入,除了 write(2)`ing 到 stdout 也。
shell 中没有分叉数据流的规定,因为没有规定在输入时加入(并行)数据流。我认为这是 Steve Bourne 的 shell 设计中一些被遗弃的想法,您可能会遇到同样的问题。
顺便说一句,研究一下使用通用dup2()
运算符的可能性,即<n>&
m>,但再次考虑,对于重定向程序,2>&3 2>&4 2>&5 2>&6
意味着您已经预先打开了 7 个文件描述符,0
...6
其中 stderr
是描述符 3
到 6
的别名(因此写入任何这些描述符的任何数据都会出现在 stderr
中)或者您可以使用 2<file_a 3<file_b 4<file_c
表示您的程序将使用从 file_a
重定向的文件描述符 2
(stderr) 执行,并且文件描述符 3
和 4
已经从文件打开 file_b
和 file_c
。可能,应该设计一些符号(现在在我看来并不容易,如何设计它)以允许在已启动的不同进程之间进行管道(使用 pipe(2)
系统调用)来做一些事情任务,但您需要构建一个通用图以实现通用性。