简体   繁体   English

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

[英]How can I implement 'tee' programmatically in C?

I'm looking for a way in C to programmatically (ie, not using redirection from the command line) implement 'tee' functionality such that my stdout goes to both stdout and a log file. 我在C中寻找一种以编程方式(即,不使用命令行重定向)实现'tee'功能的方式,以便我的stdout同时转到stdout和日志文件。 This needs to work for both my code and all linked libraries that output to stdout. 这需要适用于我的代码和输出到stdout的所有链接库。 Any way to do this? 有什么办法吗?

You could popen() the tee program. 你可以popen() tee程序。

Or you can fork() and pipe stdout through a child process such as this (adapted from a real live program I wrote, so it works!): 或者你可以通过像这样的子进程fork()和pipe 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]);  
    }
}

The " popen() tee" answers were correct. popen() tee”答案是正确的。 Here is an example program that does exactly that: 这是一个示例程序,它正是这样做的:

#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;
}

Explanation: 说明:

popen() returns a FILE* , but dup2() expects a file descriptor (fd), so fileno() converts the FILE* to an fd. popen()返回FILE* ,但dup2()需要文件描述符(fd),因此fileno()FILE*转换为fd。 Then dup2(..., STDOUT_FILENO) says to replace stdout with the fd from popen() . 然后dup2(..., STDOUT_FILENO)表示用popen()的fd替换stdout。

Meaning, you spawn a child process ( popen ) that copies all its input to stdout and a file, then you port your stdout to that process. 这意味着,您生成了一个子进程( popen ),它将所有输入复制到stdout和一个文件,然后将stdout移植到该进程。

You can use forkpty() with exec() to execute the monitored program with its parameters. 您可以将forkpty()exec()一起使用,以使用其参数执行受监视的程序。 forkpty() returns a file descriptor which is redirected to the programs stdin and stdout. forkpty()返回一个文件描述符,该描述符被重定向到程序stdin和stdout。 Whatever is written to the file descriptor is the input of the program. 无论写入文件描述符是什么,都是程序的输入。 Whatever is written by the program can be read from the file descriptor. 可以从文件描述符中读取程序写入的内容。

The second part is to read in a loop the program's output and write it to a file and also print it to stdout. 第二部分是循环读取程序的输出并将其写入文件并将其打印到stdout。

Example: 例:

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

You could use pipe(2) and dup2(2) to connect your standard out to a file descriptor you can read from. 您可以使用pipe(2)dup2(2)将标准输出连接到可以读取的文件描述符。 Then you can have a separate thread monitoring that file descriptor, writing everything it gets to a log file and the original stdout (saved avay to another filedescriptor by dup2 before connecting the pipe). 然后你可以有一个单独的线程监视该文件描述符,将它获取的所有内容写入日志文件和原始标准输出(在连接管道之前由dup2保存到另一个文件描述符)。 But you would need a background thread. 但是你需要一个后台线程。

Actually, I think the popen tee method suggested by vatine is probably simpler and safer (as long as you don't need to do anyhing extra with the log file, such as timestamping or encoding or something). 实际上,我认为vatine建议的popen tee方法可能更简单,更安全(只要您不需要对日志文件进行额外的任何操作,例如时间戳或编码等)。

There's no trivial way of doing this in C. I suspect the easiest would be to call popen(3), with tee as the command and the desired log file as an arument, then dup2(2) the file descriptor of the newly-opened FILE* onto fd 1. 在C中没有琐碎的方法。我怀疑最简单的方法是调用popen(3),将tee作为命令,将所需的日志文件作为arument,然后dup2(2)新打开的文件描述符FILE *到fd 1。

But that looks kinda ugly and I must say that I have NOT tried this. 但这看起来有点难看,我必须说我没试过这个。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM