在 OSTEP 上看到一段有意思的代码:
// p4.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
int main() {
int rc = fork();
if (rc < 0) {
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
close(STDOUT_FILENO);
close(STDERR_FILENO);
open("./p4.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
open("./p4.error", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
char *myargs[3];
myargs[0] = strdup("wc");
myargs[1] = strdup("p4.c"); // 如果将 p4.c 改成不存在的文件,标准错误就会写入到 p4.error 文件中
myargs[2] = NULL;
execvp(myargs[0], myargs);
} else {
int rc_wait = wait(NULL);
}
return 0;
}
以上程序执行结果如下:
$ gcc p4.c
$ ./a.out
$ cat p4.output
31 69 681 p4.c
可以发现以上程序并没有显式的将结果输出到屏幕,而是写入到 p4.output 文件了;同理,将程序中的 p4.c 改成不存在的文件,错误信息会写入到 p4.error 文件中。
跟以下 fprintf 这种显式指定输出文件的方式不一样,以上程序并没有显式指定标准输出为 p4.output、标准错误为 p4.error。
#include <stdio.h>
int main() {
FILE *fp = fopen("Hello", "w");
fprintf(fp, "Hello world");
return 0;
}
这是由于 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO 的默认值分别为 0,1,2。而 Unix 系统从 0 开始查找自由的文件描述符
,当 close(STDOUT_FILENO) 之后,1 变得不可用的,而通过 open() 调用打开 p4.output 文件后,该文件描述符就会对应首先被 close 的 STDOUT_FILENO;同理,p4.error 文件的文件描述符对应 STDERR_FILENO。因此,执行后续程序时,标准输出和标准错误就会分别重定向到 p4.output 和 p4.error 文件中。
Unix 管道就是以类似的方式实现的,但它用的是 pipe() 系统调用。这种情况下,一个进程的输出连接到一个内核 pipe(也就是队列);另一个进程的输入连接到同一个管道;因此,一个进程的输出会无缝地作为下一个(进程)的输入。