我怎样才能实现“tee”?用C语言编程?

发布于 2024-08-11 16:55:24 字数 107 浏览 8 评论 0原文

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

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. This needs to work for both my code and all linked libraries that output to stdout. Any way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

苏大泽ㄣ 2024-08-18 16:55:24

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

You could popen() the tee program.

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!):

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]);  
    }
}
冬天的雪花 2024-08-18 16:55:24

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

这意味着,您生成一个子进程 (popen),将其所有输入复制到 stdout 和一个文件,然后将 stdout 移植到该进程。

The "popen() tee" answers were correct. 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. Then dup2(..., STDOUT_FILENO) says to replace stdout with the fd from popen().

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.

溇涏 2024-08-18 16:55:24

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

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

You could use pipe(2) and dup2(2) to connect your standard out to a file descriptor you can read from. 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). 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).

旧城空念 2024-08-18 16:55:24

您可以将 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);

You can use forkpty() with exec() to execute the monitored program with its parameters. forkpty() returns a file descriptor which is redirected to the programs stdin and 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.

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);
蓝颜夕 2024-08-18 16:55:24

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

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

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.

But that looks kinda ugly and I must say that I have NOT tried this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文