Я ищу способ в C программно (т. е. без перенаправления из командной строки) реализовать функциональность «тройника», чтобы мой стандартный вывод переходил как в стандартный вывод, так и в файл журнала. Это должно работать как для моего кода, так и для всех связанных библиотек, которые выводятся на стандартный вывод. Любой способ сделать это?
Как я могу программно реализовать «тройник» на C?
Ответы (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]);
}
}
Ответы "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
), который копирует все свои входные данные в стандартный вывод и файл, а затем вы переносите свой стандартный вывод в этот процесс.
Вы можете использовать pipe(2)
и dup2(2)
для подключения вашего стандарта к дескриптору файла, из которого вы можете читать. Затем у вас может быть отдельный поток, отслеживающий этот файловый дескриптор, записывающий все, что он получает, в файл журнала и исходный стандартный вывод (сохраненный в другом файловом дескрипторе с помощью dup2
перед подключением канала). Но вам понадобится фоновый поток.
На самом деле, я думаю, что метод popen tee, предложенный vatine, вероятно, проще и безопаснее (если вам не нужно ничего делать с файлом журнала, например, отмечать время, кодировать или что-то в этом роде).
Вы можете использовать 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);
В C нет тривиального способа сделать это. Я подозреваю, что проще всего было бы вызвать popen(3) с tee в качестве команды и желаемым файлом журнала в качестве аргумента, а затем dup2(2) дескриптор файла только что открытого FILE* на fd 1.
Но это выглядит довольно уродливо, и я должен сказать, что я НЕ пробовал это.