Как я могу программно реализовать «тройник» на C?

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


person robottobor    schedule 19.11.2009    source источник
comment
могу ли я спросить более конкретную информацию о том, почему вам нужно такое решение? Это чтобы сэкономить на отладке ..?   -  person lorenzog    schedule 19.11.2009


Ответы (5)


Вы могли бы 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]);  
    }
}
person Will    schedule 19.11.2009
comment
+1 за то, что ничего не предполагается (например, приведенный выше код легко адаптируется к уже открытому файловому дескриптору вместо имени файла в качестве аргумента) - person Michael; 23.04.2015
comment
тройник (tee.txt); система (ftp); --› похоже, не печатает стандартный ввод или стандартный вывод? - person Victor; 02.10.2016
comment
как только я удалил: if('\n'==ch) он печатает. Я думаю, он ждал \n. Теперь функция тройника, кажется, застряла, если я не нажму Enter в конце. - person Victor; 03.10.2016

Ответы "popen() тройник" были правильными. Вот пример программы, которая делает именно это:

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

person NHDaly    schedule 18.09.2013

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

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

person Rasmus Kaj    schedule 19.11.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);
person eyalm    schedule 19.11.2009
comment
Но это означает запуск его программы из другой программы, не так ли? Предположительно, он хочет избежать этого, иначе он был бы готов просто использовать тройник. - person Benj; 19.11.2009

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

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

person Vatine    schedule 19.11.2009
comment
Пробовал это и вызывает условия гонки. Проблема может заключаться в том, что тройник иногда печатается первым. - person PALEN; 22.07.2013
comment
@PALEN Как я уже сказал, непроверенный и выглядит некрасиво. Если вы используете GNU libc, есть функциональность для создания ваших собственных FILE*-подобных объектов, и вы можете использовать ее, но тогда вы окажетесь в стране непереносимости. - person Vatine; 23.07.2013