Я ищу способ на C программно (т.е. не используя перенаправление из командной строки) реализую функцию "tee", так что мой stdout переходит в оба файла stdout и log. Это должно работать как для моего кода, так и для всех связанных библиотек, которые выводятся в stdout. Любой способ сделать это?
Как я могу реализовать "тройник" программно в C?
Ответ 1
Вы можете popen()
программу tee.
Или вы можете 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]);
}
}
Ответ 2
Ответы "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()
возвращает a FILE*
, но dup2()
ожидает файловый дескриптор (fd), поэтому fileno()
преобразует FILE*
в fd. Затем dup2(..., STDOUT_FILENO)
говорит, чтобы заменить stdout на fd из popen()
.
Смысл: вы создаете дочерний процесс (popen
), который копирует все его входные данные в stdout и файл, затем вы переносите свой stdout в этот процесс.
Ответ 3
Вы можете использовать forkpty()
с exec()
для выполнения контролируемой программы с ее параметрами. forkpty()
возвращает дескриптор файла, который перенаправляется на программы stdin и stdout. Все, что написано в дескрипторе файла, является входом программы. Все, что написано программой, можно прочитать из дескриптора файла.
Вторая часть состоит в том, чтобы читать в цикле вывод программы и записывать ее в файл, а также печатать его на 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);
Ответ 4
Вы можете использовать pipe(2)
и dup2(2)
, чтобы подключить ваш стандарт к файловому дескриптору, с которого вы можете читать. Затем вы можете иметь отдельный поток, контролирующий этот файловый дескриптор, записывая все, что попадает в файл журнала, и исходный stdout (сохраняемый avay на другой filedescriptor на dup2
перед подключением к каналу). Но вам понадобится фоновый поток.
На самом деле, я думаю, что метод popen tee, предложенный vatine, вероятно, проще и безопаснее (до тех пор, пока вам не нужно делать ничего лишнего с файлом журнала, например, timestamping или encoding или что-то еще).
Ответ 5
Нет никакого тривиального способа сделать это в C. Я подозреваю, что самым простым было бы назвать popen (3), с ти в качестве команды и желаемого файла журнала в качестве arument, затем dup2 (2) файловый дескриптор недавно открытый FILE * на fd 1.
Но это выглядит некрасиво, и я должен сказать, что я НЕ пробовал это.