如何在 C 中以编程方式实现“tee”?

Posted

技术标签:

【中文标题】如何在 C 中以编程方式实现“tee”?【英文标题】:How can I implement 'tee' programmatically in C? 【发布时间】:2009-11-19 09:20:30 【问题描述】:

我正在寻找一种在 C 语言中以编程方式(即不使用命令行重定向)实现“tee”功能的方法,以便我的标准输出可以同时发送到标准输出和日志文件。这需要对我的代码和输出到标准输出的所有链接库都有效。有什么办法吗?

【问题讨论】:

我可以问一个更具体的细节,您为什么需要这样的解决方案?是不是为了节省调试时间..? 【参考方案1】:

你可以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]);  
    

【讨论】:

+1 表示不做任何假设(例如,上面的代码很容易适应将已经打开的文件描述符而不是文件名作为参数) tee("tee.txt");系统(“ftp”); --> 似乎没有打印标准输入或标准输出任何想法? 一旦我删除: if('\n'==ch) 它会打印。我猜它正在等待\n。现在 tee 功能似乎卡住了,除非我最后按 enter。【参考方案2】:

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) 说用popen() 中的fd 替换stdout。

意思是,您生成一个子进程 (popen),将其所有输入复制到标准输出和一个文件,然后将标准输出移植到该进程。

【讨论】:

【参考方案3】:

您可以使用pipe(2)dup2(2) 将您的标准输出连接到您可以读取的文件描述符。然后你可以有一个单独的线程监视该文件描述符,将它获得的所有内容写入日志文件和原始标准输出(在连接管道之前由dup2 保存到另一个文件描述符)。但是你需要一个后台线程。

实际上,我认为vatine建议的popen tee方法可能更简单,更安全(只要您不需要对日志文件做任何额外的事情,例如时间戳或编码之类的)。

【讨论】:

【参考方案4】:

您可以使用forkpty()exec() 来执行带有参数的监控程序。 forkpty() 返回一个文件描述符,该描述符被重定向到程序 stdin 和 stdout。写入文件描述符的任何内容都是程序的输入。程序写入的任何内容都可以从文件描述符中读取。

第二部分是循环读取程序的输出并将其写入文件并打印到标准输出。

例子:

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);

【讨论】:

但这意味着从另一个程序运行他的程序不是吗?大概他想避免这种情况,否则他会愿意只使用 tee。【参考方案5】:

在 C 中没有简单的方法可以做到这一点。我怀疑最简单的方法是调用 popen(3),将 tee 作为命令,将所需的日志文件作为参数,然后 dup2(2) 的文件描述符新打开的 FILE* 到 fd 1。

但这看起来有点难看,我必须说我没有尝试过。

【讨论】:

试过这个并导致竞争条件。问题可能是 tee 有时会先打印。 @PALEN 正如我所说,“未经测试,看起来很丑”。如果您使用的是 GNU libc,则有一个功能可以创建您自己的类似 FILE* 的对象,您可以使用它,但您最终会陷入不可移植的境地。

以上是关于如何在 C 中以编程方式实现“tee”?的主要内容,如果未能解决你的问题,请参考以下文章

如何在目标 c 中以编程方式添加约束

如何在 Objective C 中以编程方式设置集合视图?

如何在 C/C++ 中以编程方式查找“保存的游戏”文件夹?

如何在 c#.net 中以编程方式重新启动我的窗口服务

如何在iphone中以编程方式实现带有背景图像的按钮

如何在核心数据中以编程方式创建超类/子类?