HOWTO · C

Die dup2-Funktion in C

Dieser Artikel demonstriert, wie die Funktion dup2 in C verwendet wird

Auf dieser Seite

In POSIX-C sorgt dup2(oldfd, newfd) dafür, dass newfd auf dieselbe offene Dateibeschreibung verweist wie oldfd. Die Funktion wird häufig verwendet, um vor dem Lesen aus oder Schreiben in eine Datei die Standardeingabe oder -ausgabe umzuleiten. Prüfen Sie den Rückgabewert: Bei einem Fehler liefert der Aufruf -1 und setzt errno.

Was dup2 tut und wann Sie die Funktion verwenden

Die Deklaration lautet int dup2(int oldfd, int newfd); und ist über <unistd.h> verfügbar. Bei Erfolg gibt die Funktion newfd zurück. War newfd bereits geöffnet, schließt dup2 diesen Deskriptor und verwendet ihn erneut. Das Schließen und Duplizieren erfolgt atomar. Daher eignet sich die Funktion für Umleitungen in Code, der möglicherweise parallel in mehreren Threads ausgeführt wird.

Beide Deskriptoren verweisen auf dieselbe offene Dateibeschreibung. Deshalb teilen sie sich den Dateiversatz und die Dateistatusflags: Ein Lese- oder Schreibzugriff über einen der beiden Deskriptoren verschiebt denselben Dateiversatz. Deskriptorflags gelten dagegen für den jeweiligen Deskriptor. Der von dup2 erstellte Deskriptor übernimmt FD_CLOEXEC nicht von oldfd.

Sind oldfd und newfd gleich, gibt dup2 diesen Deskriptor zurück, ohne ihn zu schließen. Der Deskriptor muss bereits gültig sein. Dateideskriptornummern gelten nur innerhalb eines Prozesses und hängen davon ab, welche Deskriptoren geöffnet sind. Gehen Sie daher weder davon aus, dass eine geöffnete Datei immer den Deskriptor 3 erhält, noch davon, dass jeder Prozess genau drei offene Deskriptoren hat.

Standardausgabe mit dup2 umleiten

Öffnen Sie ein Ziel zum Schreiben und duplizieren Sie dessen Deskriptor anschließend auf STDOUT_FILENO. Dieses kompakte Programm leitet die Ausgabe in eine temporäre Datei um, stellt die ursprüngliche Standardausgabe wieder her und gibt die aufgezeichnete Zeile aus, damit das Ergebnis sichtbar ist. Es verwendet POSIX-Funktionen für temporäre Dateien und Dateideskriptoren.

Führen Sie das folgende Beispiel aus:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

#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

Der bereitgestellte Lauf hat Captured: Hello through dup2 aufgezeichnet. Das Programm schreibt seine erste Zeile in die temporäre Datei, stellt die Standardausgabe wieder her, liest die gespeicherten Bytes und gibt die aufgezeichnete Zeile aus. Die Beschriftung Captured: im unveränderten Beispiel bleibt auf Englisch. Wählen Sie in einem Produktivprogramm einen zum Inhalt passenden Zielpfad und geeignete Berechtigungen. Das Beispiel mit mkstemp erstellt eine private temporäre Datei.

Standardeingabe mit dup2 umleiten

Damit ein Programm über die Standardeingabe aus einer Datei liest, öffnen Sie die Datei schreibgeschützt und duplizieren ihren Deskriptor auf STDIN_FILENO. Nach erfolgreichem Aufruf lesen Funktionen wie fgets, die stdin verwenden, aus dieser Datei. Den von open zurückgegebenen Deskriptor können Sie nach dem Duplizieren schließen, da die Standardeingabe nun auf dieselbe offene Dateibeschreibung verweist.

Dieses vollständige Beispiel erstellt eine temporäre Eingabedatei, leitet die Standardeingabe auf diese Datei um und liest mit fgets eine Zeile. Es gibt die Zeile aus, damit Sie sehen, was gelesen wurde. Die temporäre Datei dient als Eingabe des Beispiels. Wenn Sie das Muster auf eine vorhandene Datei übertragen, passen Sie deren Inhalt oder die fgets-Logik entsprechend an.

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

#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;
}

Die Variante mit open für einen tatsächlichen Eingabepfad lautet int input = open("input.txt", O_RDONLY);. Prüfen Sie auf -1, rufen Sie dup2(input, STDIN_FILENO) auf und schließen Sie input nach erfolgreichem Aufruf. Nachfolgende Lesezugriffe über stdin beziehen ihre Daten aus dieser Datei. Im vollständigen Beispiel liest dieselbe Abfolge die in die temporäre Datei geschriebene Zeile. Schlägt das Öffnen, Duplizieren oder Lesen fehl, behandeln Sie den Fehler und schließen Sie alle Deskriptoren, über die Ihr Code noch verfügt.

Unterschiede zwischen dup und dup2

Beide Funktionen erstellen einen weiteren Deskriptor für dieselbe offene Dateibeschreibung. dup(oldfd) wählt den Deskriptor mit der niedrigsten Nummer, der gerade verfügbar ist, und gibt ihn zurück. dup2(oldfd, newfd) verwendet die von Ihnen angegebene Nummer und schließt einen dort bereits geöffneten Deskriptor zuerst. Verwenden Sie dup, wenn eine beliebige verfügbare Nummer genügt. Verwenden Sie dup2, wenn ein bestimmter Deskriptor, häufig die Standardeingabe oder -ausgabe, auf dieselbe Quelle verweisen soll.

Zum Beispiel liefert int copy = dup(file); einen neuen Deskriptor oder bei einem Fehler -1. Prüfen Sie den Wert, bevor Sie copy verwenden, und schließen Sie beide Deskriptoren, sobald Sie sie nicht mehr benötigen. Die zurückgegebene Nummer ist nicht garantiert 3; sie hängt davon ab, welche Deskriptoren im Prozess geöffnet sind.

Fehler und Lebensdauer von Deskriptoren behandeln

Prüfen Sie jedes Ergebnis von open, dup und dup2, bevor Sie den zurückgegebenen Deskriptor verwenden. Bei einem Fehler liefert der Aufruf -1 und setzt errno; perror("dup2") kann den aktuellen Fehler melden. Verwenden Sie einen fehlgeschlagenen Rückgabewert nicht als Deskriptor. Schlägt ein Aufruf in einer mehrstufigen Umleitung fehl, schließen Sie die Deskriptoren, die Ihr Code geöffnet oder gespeichert hat. Stellen Sie außerdem die Standardausgabe oder -eingabe wieder her, falls sie bereits geändert wurde und das Programm mit dem ursprünglichen Datenstrom fortfahren muss.

Nach einer erfolgreichen Umleitung macht das Schließen des ursprünglichen Quelldeskriptors die Umleitung nicht rückgängig: Der Standarddeskriptor bleibt geöffnet und verweist weiterhin auf dieselbe offene Dateibeschreibung. Schließen Sie gespeicherte Deskriptoren und weitere Duplikate, sobald sie nicht mehr benötigt werden. Leeren Sie gepufferte Ausgaben, bevor Sie STDOUT_FILENO ändern oder wiederherstellen. Die Puffer der C-Standardbibliothek sind von den Dateideskriptoroperationen des Kernels getrennt. Mit diesen Prüfungen lassen sich Deskriptoren zuverlässig ersetzen und aufräumen, ohne feste Deskriptornummern vorauszusetzen.