GNU/Linux >> Tutoriels Linux >  >> Linux

Pourquoi stdout a-t-il besoin d'un vidage explicite lorsqu'il est redirigé vers un fichier ?

Vous combinez à tort les fonctions IO tamponnées et non tamponnées. Une telle combinaison doit être faite avec beaucoup de soin, surtout lorsque le code doit être portable. (et c'est mal d'écrire du code non portable...)
Il est certainement préférable d'éviter de combiner des E/S tamponnées et non tamponnées sur le même descripteur de fichier.

E/S mises en mémoire tampon : fprintf() , fopen() , fclose() , freopen() ...

E/S sans tampon : write() , open() , close() , dup() ...

Lorsque vous utilisez dup2() pour rediriger stdout. La fonction n'a pas connaissance du tampon qui a été rempli par fprintf() . Alors quand dup2() ferme l'ancien descripteur 1, il ne vide pas le tampon et le contenu pourrait être vidé vers une sortie différente. Dans votre cas 2a, il a été envoyé au /dev/null .

La solution

Dans votre cas, il est préférable d'utiliser freopen() au lieu de dup2() . Cela résout tous vos problèmes :

  1. Il vide les tampons du FILE d'origine flux. (cas 2a)
  2. Il définit le mode de mise en mémoire tampon en fonction du fichier nouvellement ouvert. (cas 3)

Voici l'implémentation correcte de votre fonction :

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

Malheureusement, avec les E/S tamponnées, vous ne pouvez pas définir directement les autorisations d'un fichier nouvellement créé. Vous devez utiliser d'autres appels pour modifier les autorisations ou vous pouvez utiliser des extensions glibc non portables. Voir le fopen() man page .


Rinçage pour stdout est déterminé par son comportement tampon. La mise en mémoire tampon peut être définie sur trois modes :_IOFBF (mise en mémoire tampon complète :attend jusqu'à fflush() si possible), _IOLBF (mise en mémoire tampon de ligne :nouvelle ligne déclenche le vidage automatique) et _IONBF (écriture directe toujours utilisée). "La prise en charge de ces caractéristiques est définie par l'implémentation et peut être affectée via le setbuf() et setvbuf() fonctions." [C99:7.19.3.3]

"Au démarrage du programme, trois flux de texte sont prédéfinis et n'ont pas besoin d'être ouverts explicitement - entrée standard (pour lire l'entrée conventionnelle), sortie standard (pour écrire la sortie conventionnelle) et erreur standard (pour écrire la sortie de diagnostic). Comme initialement ouvert, l'erreur standard le flux n'est pas entièrement mis en mémoire tampon ; les flux d'entrée et de sortie standard sont entièrement mis en mémoire tampon si et seulement si le flux peut être déterminé comme ne faisant pas référence à un appareil interactif." [C99:7.19.3.7]

Explication du comportement observé

Donc, ce qui se passe, c'est que l'implémentation fait quelque chose de spécifique à la plate-forme pour décider si stdout va être tamponné en ligne. Dans la plupart des implémentations de la libc, ce test est effectué lors de la première utilisation du flux.

  1. Le comportement 1 s'explique facilement :lorsque le flux est destiné à un appareil interactif, il est mis en mémoire tampon, et le printf() est vidé automatiquement.
  2. Le cas 2 est également attendu :lorsque nous redirigeons vers un fichier, le flux est entièrement mis en mémoire tampon et ne sera pas vidé, sauf avec fflush() , à moins que vous n'y écriviez des tas de données.
  3. Enfin, nous comprenons également le cas n° 3 pour les implémentations qui n'effectuent qu'une seule fois la vérification du fd sous-jacent. Parce que nous avons forcé le tampon de stdout à être initialisé dans le premier printf() , stdout a acquis le mode de mise en mémoire tampon de ligne. Lorsque nous échangeons le fd pour accéder au fichier, il est toujours mis en mémoire tampon, de sorte que les données sont automatiquement vidées.

Quelques implémentations réelles

Chaque libc a une certaine latitude dans la façon dont elle interprète ces exigences, car C99 ne spécifie pas ce qu'est un "périphérique interactif", et l'entrée stdio de POSIX ne l'étend pas non plus (au-delà d'exiger que stderr soit ouvert en lecture).

  1. Glibc. Voir filedoalloc.c:L111. Ici, nous utilisons stat() pour tester si le fd est un tty et définir le mode de mise en mémoire tampon en conséquence. (Ceci est appelé depuis fileops.c.) stdout a initialement un tampon nul, et il est alloué lors de la première utilisation du flux en fonction des caractéristiques de fd 1.

  2. BSD libc. Code très similaire, mais beaucoup plus propre à suivre ! Voir cette ligne dans makebuf.c


Linux
  1. Pourquoi la valeur d'inode change lorsque nous éditons dans l'éditeur "vi" ?

  2. Pourquoi l'option Ssh -t ajoute-t-elle Cr &Lf dans la sortie redirigée ?

  3. Pourquoi ai-je besoin d'un bit 'execute' en mode fichier sur les systèmes de fichiers Unix ?

  4. Pourquoi clang génère-t-il du texte inintelligible lorsqu'il est redirigé ?

  5. Où vont les métadonnées lorsque vous enregistrez un fichier ?

Pourquoi clang a-t-il encore besoin de libgcc.a pour compiler mon code ?

Pourquoi rsync n'utilise-t-il pas delta-transfer pour les fichiers locaux ?

Comment obtenir un fichier avec un nom correct lors de la redirection ?

Pourquoi wget'ing une image me donne-t-il un fichier, pas une image ?

Pourquoi Linux a-t-il besoin d'avoir à la fois `/dev/cdrom` et `/media/cdrom` ?

Pourquoi un périphérique RAID 10 doit-il être initialisé ?