GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Warum muss stdout explizit geleert werden, wenn es in eine Datei umgeleitet wird?

Sie kombinieren fälschlicherweise gepufferte und ungepufferte IO-Funktionen. Eine solche Kombination muss sehr sorgfältig durchgeführt werden, insbesondere wenn der Code portabel sein muss. (und es ist schlecht, nicht portierbaren Code zu schreiben ...)
Es ist sicherlich am besten zu vermeiden, gepufferte und ungepufferte IO auf demselben Dateideskriptor zu kombinieren.

Gepufferte E/A: fprintf() , fopen() , fclose() , freopen() ...

Unbuffered IO: write() , open() , close() , dup() ...

Wenn Sie dup2() verwenden um stdout umzuleiten. Der Funktion ist der von fprintf() gefüllte Puffer nicht bekannt . Also wenn dup2() schließt den alten Deskriptor 1, es löscht den Puffer nicht und der Inhalt könnte in eine andere Ausgabe geräumt werden. In Ihrem Fall 2a wurde es an /dev/null gesendet .

Die Lösung

In Ihrem Fall verwenden Sie am besten freopen() statt dup2() . Dies löst alle Ihre Probleme:

  1. Es leert die Puffer des ursprünglichen FILE Strom. (Fall 2a)
  2. Es stellt den Puffermodus entsprechend der neu geöffneten Datei ein. (Fall 3)

Hier ist die korrekte Implementierung Ihrer Funktion:

void RedirectStdout2File(const char* log_path) {
    if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}

Leider können Sie mit gepuffertem IO die Berechtigungen einer neu erstellten Datei nicht direkt festlegen. Sie müssen andere Aufrufe verwenden, um die Berechtigungen zu ändern, oder Sie können nicht portierbare glibc-Erweiterungen verwenden. Siehe fopen() man page .


Spülen für stdout wird durch sein Pufferverhalten bestimmt. Die Pufferung kann auf drei Modi eingestellt werden:_IOFBF (volle Pufferung:wartet bis fflush() wenn möglich), _IOLBF (Zeilenpufferung:Zeilenumbruch löst automatisches Leeren aus) und _IONBF (direktes Schreiben wird immer verwendet). „Die Unterstützung dieser Eigenschaften ist implementierungsabhängig und kann über setbuf() beeinflusst werden und setvbuf() Funktionen." [C99:7.19.3.3]

„Beim Programmstart sind drei Textströme vordefiniert und müssen nicht explizit geöffnet werden – Standardeingabe (zum Lesen der konventionellen Eingabe), Standardausgabe (zum Schreiben der konventionellen Ausgabe) und Standardfehler (zum Schreiben der Diagnoseausgabe). Stream nicht vollständig gepuffert ist; die Standardeingabe- und Standardausgabestreams werden vollständig gepuffert, wenn und nur wenn festgestellt werden kann, dass der Stream nicht auf ein interaktives Gerät verweist." [C99:7.19.3.7]

Erklärung des beobachteten Verhaltens

Was also passiert, ist, dass die Implementierung etwas Plattformspezifisches tut, um zu entscheiden, ob stdout wird zeilengepuffert. In den meisten libc-Implementierungen wird dieser Test durchgeführt, wenn der Stream zum ersten Mal verwendet wird.

  1. Verhalten Nr. 1 lässt sich leicht erklären:Wenn der Stream für ein interaktives Gerät bestimmt ist, wird er zeilengepuffert und der printf() wird automatisch gespült.
  2. Fall #2 wird jetzt auch erwartet:Wenn wir zu einer Datei umleiten, ist der Stream vollständig gepuffert und wird nicht geleert, außer mit fflush() , es sei denn, Sie schreiben Unmengen von Daten darauf.
  3. Schließlich verstehen wir Fall #3 auch für Implementierungen, die die Überprüfung des zugrunde liegenden fd nur einmal durchführen. Weil wir die Initialisierung des Puffers von stdout im ersten printf() erzwungen haben , stdout hat den zeilengepufferten Modus übernommen. Wenn wir das fd auslagern, um in die Datei zu gehen, ist es immer noch zeilengepuffert, sodass die Daten automatisch geleert werden.

Einige aktuelle Implementierungen

Jede libc hat Spielraum, wie sie diese Anforderungen interpretiert, da C99 weder spezifiziert, was ein "interaktives Gerät" ist, noch erweitert der stdio-Eintrag von POSIX dies (außer dass stderr zum Lesen geöffnet sein muss).

  1. Glibc. Siehe filedoalloc.c:L111. Hier verwenden wir stat() um zu testen, ob fd ein tty ist, und stellen Sie den Puffermodus entsprechend ein. (Dies wird von fileops.c aufgerufen.) stdout hat zunächst einen Nullpuffer und wird bei der ersten Verwendung des Streams basierend auf den Eigenschaften von fd 1 zugewiesen.

  2. BSD-libc. Sehr ähnlicher, aber viel saubererer Code folgt! Siehe diese Zeile in makebuf.c


Linux
  1. Warum ändert sich der Inode-Wert, wenn wir im „vi“-Editor bearbeiten?

  2. Warum benötigt der Root-Benutzer eine Sudo-Berechtigung?

  3. Warum funktioniert „zip“ in einer For-Schleife, wenn die Datei existiert, aber nicht, wenn sie nicht existiert?

  4. Warum bedeutet ENOENT No such file or directory?

  5. Warum brauchen wir die .so.1-Datei unter Linux?

Warum wird diese Shell-Pipeline beendet?

Warum verwendet rsync keine Delta-Übertragung für lokale Dateien?

Warum generiert Clang bei der Weiterleitung unverständlichen Text?

Wohin gehen Metadaten, wenn Sie eine Datei speichern?

Warum verwendet Linux eine Swap-Partition und keine Datei?

Warum muss Linux sowohl `/dev/cdrom` als auch `/media/cdrom` haben?