Ich suche nach einem Weg in C programmgesteuert (dh keine Umleitung von der Befehlszeile verwenden) implementieren "Tee" -Funktionalität, so dass mein stdout sowohl stdout und eine Protokolldatei geht. Dies muss sowohl für meinen Code als auch für alle verbundenen Bibliotheken funktionieren, die an stdout ausgegeben werden. Irgendeine Möglichkeit, dies zu tun?Wie kann ich 'Tee' programmgesteuert in C implementieren?
Antwort
Sie könnten popen()
das Tee-Programm.
Oder Sie können fork()
und Rohr stdout
durch einen untergeordneten Prozess wie diese (angepasst von einem echten Live-Programm, das ich geschrieben habe, so funktioniert es!):
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]);
}
}
+1 für die Annahme nichts (zum Beispiel, der obige Code ist leicht Adapter, um einen bereits geöffneten Dateideskriptor anstelle eines Dateinamens als Argument zu nehmen) – Michael
tee ("tee.txt "); System (" ftp "); -> scheint nicht zu drucken stdin oder stdout keine Idee? – Victor
sobald ich entfernte: if ('\ n' == ch) es druckt. Ich denke, es war auf \ warten n. Jetzt scheint die Tee-Funktion fest, es sei denn, ich drücke Enter am Ende. – Victor
Es gibt keine triviale Weise, dies in C zu tun Ich vermute, das einfachste wäre, popen (3) mit tee als Befehl und der gewünschten Protokolldatei als Argument aufzurufen, dann dup2 (2) den Dateideskriptor der neu geöffneten FILE * auf fd 1.
Aber das sieht irgendwie hässlich aus und ich muss sagen, dass ich das NICHT versucht habe.
Versucht dies und verursacht Race-Bedingungen. Ein Problem kann sein, dass Tee zuerst manchmal. – PALEN
@PALEN Wie gesagt, "ungeprüft und sieht hässlich". Wenn Sie GNU libc verwenden, gibt es eine Funktion zum Erstellen Ihrer eigenen FILE * -ähnlichen Objekte und Sie könnten diese verwenden, aber dann landen Sie in Unportability-Land. – Vatine
Sie können forkpty()
mit exec()
verwenden, um das überwachte Programm mit seinen Parametern auszuführen. forkpty()
gibt einen Dateideskriptor zurück, der zu den Programmen stdin und stdout umgeleitet wird. Was immer in den Dateideskriptor geschrieben wird, ist die Eingabe des Programms. Was auch immer vom Programm geschrieben wird, kann aus dem Dateideskriptor gelesen werden.
Der zweite Teil ist, in einer Schleife die Ausgabe des Programms zu lesen und es in eine Datei zu schreiben und es auch auf stdout auszudrucken.
Beispiel:
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);
Aber dies bedeutet, sein Programm von einem anderen Programm nicht auszuführen Vermutlich will er das vermeiden, sonst wäre er bereit, einfach Tee zu benutzen. – Benj
könnten Sie verwenden pipe(2)
und dup2(2)
Ihre Standard, um einen Dateideskriptor verbinden Sie aus lesen kann. Dann können Sie einen separaten Thread haben, der diesen Dateideskriptor überwacht, alles schreibt, was er in eine Protokolldatei holt, und das ursprüngliche stdout (gespeicherte avay in einen anderen Dateideskriptor durch dup2
, bevor die Pipe verbunden wird). Aber Sie würden einen Hintergrundthread benötigen.
Eigentlich denke ich, die Popen-Tee-Methode vorgeschlagen von Vatine ist wahrscheinlich einfacher und sicherer (solange Sie nicht extra mit der Protokolldatei, wie Timestamping oder Codierung oder etwas tun müssen).
Die Antworten "popen()
tee" waren korrekt. Hier ist ein Beispielprogramm, das genau das tut:
#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;
}
Erläuterung:
popen()
gibt ein FILE*
, aber dup2()
erwartet einen Dateideskriptor (fd), so fileno()
die FILE*
auf einen fd umwandelt. Dann dup2(..., STDOUT_FILENO)
sagt, stdout mit dem fd von popen()
zu ersetzen.
Das heißt, Sie spawnen einen Kind-Prozess (popen
), der alle seine Eingabe in stdout und eine Datei kopiert, dann portieren Sie Ihr stdout zu diesem Prozess.
kann ich ein genaueres Detail fragen, warum brauchen Sie eine solche Lösung? Soll es beim Debuggen eingespart werden ..? – lorenzog