HOWTO · C

La fonction dup2 en C

Cet article explique comment utiliser la fonction `dup2` en C pour rediriger l’entrée ou la sortie standard et gérer les descripteurs de fichier.

Sur cette page

En C POSIX, dup2(oldfd, newfd) fait référer newfd à la même description de fichier ouvert que oldfd. Cette fonction sert souvent à rediriger l’entrée ou la sortie standard avant de lire depuis un fichier ou d’y écrire. Vérifiez sa valeur de retour : en cas d’échec, l’appel renvoie -1 et définit errno.

Rôle de dup2 et cas d’utilisation

La déclaration est int dup2(int oldfd, int newfd); et est disponible via <unistd.h>. En cas de réussite, la fonction renvoie newfd. Si newfd était déjà ouvert, dup2 le ferme puis réutilise ce descripteur. Cette opération de remplacement est atomique pour la séquence fermeture-duplication, ce qui la rend utile pour la redirection dans du code susceptible de s’exécuter en parallèle avec d’autres threads.

Les deux descripteurs désignent une même description de fichier ouvert. Ils partagent donc le décalage dans le fichier et les indicateurs d’état du fichier : toute lecture ou écriture via l’un ou l’autre fait avancer ce même décalage. Les indicateurs de descripteur sont propres à chaque descripteur ; le nouveau descripteur créé par dup2 n’hérite pas de FD_CLOEXEC depuis oldfd.

Si oldfd et newfd sont égaux, dup2 renvoie ce descripteur sans le fermer. Celui-ci doit déjà être valide. Les numéros de descripteur de fichier dépendent du processus et des descripteurs qui y sont ouverts. Ne supposez pas que l’ouverture d’un fichier renvoie toujours le descripteur 3, ni que chaque processus possède exactement trois descripteurs ouverts.

Rediriger la sortie standard avec dup2

Ouvrez une destination en écriture, puis dupliquez son descripteur vers STDOUT_FILENO. Ce programme compact redirige la sortie vers un fichier temporaire, rétablit la sortie standard d’origine et affiche la ligne capturée pour rendre le résultat visible. Il utilise les fonctions POSIX de gestion des fichiers temporaires et des descripteurs.

Exécutez cet exemple :

Le code et les libellés ou sorties affichés dans cette section restent en anglais.

#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    char path[] = "/tmp/dup2-demo-XXXXXX";
    int file = mkstemp(path);
    if (file == -1) return 1;

    int saved_stdout = dup(STDOUT_FILENO);
    if (saved_stdout == -1) {
        close(file);
        unlink(path);
        return 1;
    }

    if (dup2(file, STDOUT_FILENO) == -1) {
        close(saved_stdout);
        close(file);
        unlink(path);
        return 1;
    }

    int write_ok = puts("Hello through dup2") != EOF && fflush(stdout) == 0;
    int restore_ok = dup2(saved_stdout, STDOUT_FILENO) != -1;
    close(saved_stdout);
    if (!write_ok || !restore_ok) {
        close(file);
        unlink(path);
        return 1;
    }

    if (lseek(file, 0, SEEK_SET) == (off_t)-1) {
        close(file);
        unlink(path);
        return 1;
    }

    char buffer[64];
    ssize_t length = read(file, buffer, sizeof(buffer) - 1);
    close(file);
    unlink(path);
    if (length < 0) return 1;
    buffer[length] = '\0';
    if (printf("Captured: %s", buffer) < 0) return 1;
    return 0;
}
Captured: Hello through dup2

L’exécution fournie a capturé Captured: Hello through dup2. Le programme écrit d’abord une ligne dans le fichier temporaire, rétablit la sortie standard, lit les octets enregistrés, puis affiche cette ligne. Dans un programme de production, choisissez un chemin de destination et des permissions adaptés aux données ; l’exemple avec mkstemp crée un fichier temporaire privé.

Rediriger l’entrée standard avec dup2

Pour qu’un programme lise un fichier via l’entrée standard, ouvrez le fichier en lecture seule, puis dupliquez son descripteur vers STDIN_FILENO. Après la réussite de l’appel, les fonctions qui lisent depuis stdin, comme fgets, lisent depuis ce fichier. Vous pouvez fermer le descripteur renvoyé par open après la duplication, car l’entrée standard désigne alors la même description de fichier ouvert.

Cet exemple complet crée un fichier d’entrée temporaire, redirige l’entrée standard vers celui-ci et lit une ligne avec fgets. Il affiche la ligne pour montrer ce qui a été lu. Le fichier temporaire sert d’entrée à l’exemple ; pour adapter cette méthode à un fichier existant, remplacez son contenu ou modifiez la logique de fgets.

Le code et les libellés ou sorties affichés dans cette section restent en anglais.

#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    char path[] = "/tmp/dup2-input-XXXXXX";
    int input = mkstemp(path);
    if (input == -1) return 1;

    const char line[] = "Read through redirected stdin\n";
    ssize_t written = write(input, line, sizeof line - 1);
    if (written != (ssize_t)(sizeof line - 1) || lseek(input, 0, SEEK_SET) == (off_t)-1) {
        close(input);
        unlink(path);
        return 1;
    }

    if (dup2(input, STDIN_FILENO) == -1) {
        close(input);
        unlink(path);
        return 1;
    }
    close(input);
    unlink(path);

    char buffer[80];
    if (fgets(buffer, sizeof buffer, stdin) == NULL) return 1;
    if (printf("Read: %s", buffer) < 0) return 1;
    return 0;
}

La forme utilisant open pour un véritable chemin d’entrée est int input = open("input.txt", O_RDONLY); : vérifiez que le résultat n’est pas -1, appelez dup2(input, STDIN_FILENO), puis fermez input après la réussite. Les lectures suivantes depuis stdin consommeront ce fichier. Dans l’exemple complet, la même séquence lit la ligne écrite dans le fichier temporaire. En cas d’échec de l’ouverture, de la duplication ou de la lecture, gérez l’erreur et fermez chaque descripteur que votre code possède encore.

Différences entre dup et dup2

Les deux fonctions créent un autre descripteur qui désigne la même description de fichier ouvert. dup(oldfd) choisit et renvoie le descripteur disponible portant le plus petit numéro. dup2(oldfd, newfd) cible le numéro que vous indiquez et ferme d’abord le descripteur cible s’il est ouvert. Utilisez dup lorsqu’un numéro quelconque convient ; utilisez dup2 lorsqu’un descripteur précis, généralement l’entrée ou la sortie standard, doit désigner la source.

Par exemple, int copy = dup(file); renvoie un nouveau descripteur ou -1 en cas d’échec. Vérifiez le résultat avant d’utiliser copy, puis fermez les deux descripteurs lorsqu’ils ne sont plus nécessaires. Le numéro renvoyé n’est pas nécessairement 3 ; il dépend des descripteurs ouverts dans le processus.

Gérer les erreurs et la durée de vie des descripteurs

Vérifiez le résultat de chaque appel à open, dup et dup2 avant d’utiliser le descripteur renvoyé. Un échec renvoie -1 et définit errno ; perror("dup2") peut afficher l’erreur courante. N’utilisez pas un résultat d’échec comme descripteur. Si un appel échoue au cours d’une séquence de redirection en plusieurs étapes, fermez les descripteurs que votre code a ouverts ou sauvegardés, et rétablissez l’entrée ou la sortie standard si elle a déjà été modifiée et que le programme doit continuer avec le flux d’origine.

Après une redirection réussie, fermer le descripteur source d’origine n’annule pas la redirection : le descripteur standard reste ouvert et désigne la description de fichier ouvert partagée. Fermez les descripteurs sauvegardés et les autres duplications lorsqu’ils ne sont plus nécessaires. Videz les tampons de sortie avant de modifier ou de rétablir STDOUT_FILENO ; les tampons de la bibliothèque C stdio sont distincts des opérations du noyau sur les descripteurs. Ces vérifications rendent prévisibles le remplacement et la fermeture des descripteurs, sans supposer de numéros particuliers.