我想在C语言中以编程方式实现'tee'的功能,使得我的stdout输出既能够到标准输出流,又能够记录到一个日志文件中,而不是通过命令行重定向实现。这需要同时适用于我的代码和所有输出到stdout的链接库。有没有什么方法可以实现这个功能?
您可以使用popen()
函数来调用tee程序。
或者您可以使用fork()
函数,并通过子进程将stdout
进行管道传输,例如下面这个示例(该示例是我写的一个真实程序,所以它可行!):
void tee(const char* fname) {
int pipe_fd[2];
check(pipe(pipe_fd));
const pid_t pid = fork();
check(pid);
if(!pid) { // our log child
close(pipe_fd[1]); // Close unused write end
FILE* logFile = fname? fopen(fname,"a"): NULL;
if(fname && !logFile)
fprintf(stderr,"cannot open log file \"%s\": %d (%s)\n",fname,errno,strerror(errno));
char ch;
while(read(pipe_fd[0],&ch,1) > 0) {
//### any timestamp logic or whatever here
putchar(ch);
if(logFile)
fputc(ch,logFile);
if('\n'==ch) {
fflush(stdout);
if(logFile)
fflush(logFile);
}
}
putchar('\n');
close(pipe_fd[0]);
if(logFile)
fclose(logFile);
exit(EXIT_SUCCESS);
} else {
close(pipe_fd[0]); // Close unused read end
// redirect stdout and stderr
dup2(pipe_fd[1],STDOUT_FILENO);
dup2(pipe_fd[1],STDERR_FILENO);
close(pipe_fd[1]);
}
}
popen()
tee"的答案是正确的。这是一个完全实现这一功能的示例程序:#include "stdio.h"
#include "unistd.h"
int main (int argc, const char * argv[])
{
printf("pre-tee\n");
if(dup2(fileno(popen("tee out.txt", "w")), STDOUT_FILENO) < 0) {
fprintf(stderr, "couldn't redirect output\n");
return 1;
}
printf("post-tee\n");
return 0;
}
解释:
popen()
返回一个 FILE*
,但 dup2()
需要一个文件描述符(fd),因此 fileno()
将 FILE*
转换为 fd。然后,dup2(..., STDOUT_FILENO)
表示用来替换 stdout 的 fd 是从 popen()
得到的。
也就是说,你会产生一个子进程(popen
),它将所有的输入都复制到 stdout 和一个文件中,然后将你的 stdout 重定向到该进程。
pipe(2)
和dup2(2)
将您的标准输出连接到一个文件描述符,然后可以有一个单独的线程监视该文件描述符,并将其收到的所有内容写入日志文件和原始的标准输出(通过dup2
保存到另一个文件描述符中再连接管道之前)。但是需要一个后台线程。您可以使用forkpty()
和exec()
来执行带有参数的监视程序。forkpty()
返回一个文件描述符,该文件描述符被重定向到程序的标准输入和标准输出。无论写入文件描述符的内容是什么,都将作为程序的输入。程序写入的任何内容都可以从文件描述符中读取。
第二部分是循环读取程序的输出并将其写入文件,同时将其打印到标准输出。
示例:
pid = forkpty(&fd, NULL, NULL, NULL);
if (pid<0)
return -1;
if (!pid) /* Child */
{
execl("/bin/ping", "/bin/ping", "-c", "1", "-W", "1", "192.168.3.19", NULL);
}
/* Parent */
waitpid(pid, &status, 0);
return WEXITSTATUS(status);
在C语言中没有简单的方法来实现这个。我猜最简单的方法是使用popen(3)调用tee命令和所需的日志文件作为参数,然后将新打开的FILE*文件描述符重定向到fd 1。
但是看起来有点丑陋,我必须说我还没有尝试过这个方法。