Как я могу реализовать «ти» программно в C? - PullRequest
12 голосов
/ 19 ноября 2009

Я ищу способ в C программно (то есть, не использовать перенаправление из командной строки) реализовать функциональность 'tee' так, чтобы мой стандартный вывод шел как в стандартный вывод, так и в файл журнала. Это должно работать как для моего кода, так и для всех связанных библиотек, которые выводят на стандартный вывод. Есть ли способ сделать это?

Ответы [ 5 ]

7 голосов
/ 19 ноября 2009

Вы можете popen() программу тройника.

Или вы можете 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]);  
    }
}
6 голосов
/ 19 сентября 2013

Ответы "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), который копирует все свои входные данные в стандартный вывод и файл, а затем переносите свой стандартный вывод в этот процесс.

1 голос
/ 19 ноября 2009

Вы можете использовать pipe(2) и dup2(2) для подключения вашего стандарта к дескриптору файла, из которого вы можете читать. Затем у вас может быть отдельный поток, отслеживающий дескриптор файла, записывающий все, что он получает, в файл журнала и исходный стандартный вывод (сохраненный в другом файловом дескрипторе с помощью dup2 перед подключением канала). Но вам нужен фоновый поток.

На самом деле, я думаю, что метод popen tee, предложенный vatine, возможно, проще и безопаснее (если вам не нужно ничего делать с файлом журнала, например, с отметкой времени или кодированием или чем-то еще).

1 голос
/ 19 ноября 2009

Вы можете использовать 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);
0 голосов
/ 19 ноября 2009

Нет простого способа сделать это в C. Я подозреваю, что проще всего было бы вызвать popen (3) с командой tee в качестве команды и желаемым файлом журнала в качестве arument, а затем dup2 (2) файловым дескриптором недавно открытый ФАЙЛ * на ФД 1.

Но это выглядит некрасиво, и я должен сказать, что я НЕ пробовал это.

...