UNIXwork

Externes Programm mit stdin/stdout

22. August 2016

Um in C ein externes Programm auszuführen, und dabei auf die Standardein- und Ausgabe des Programms zugreifen, muss man dies per Hand mit ein paar Syscalls machen. Es gibt zwar auch die Funktion popen, aber damit kann man nur in stdin des Programms schreiben, oder stdout lesen.

Programme werden in der Unixwelt gestartet, in dem mit fork der aktuelle Prozess kopiert wird. In dem neuen Kind-Prozess wird dann mit exec das gewünschte Programm ausgeführt.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char **argv) {
    pid_t pid = fork();
    if(pid == 0) {
        // child

        return execl("/usr/bin/echo", "echo", "hello world");
    } else if(pid > 0) {
        // parent

        int status = -1;
        waitpid(pid, &status, 0); // wait for child termination
    } else {
        perror("fork");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

Dies ist ein einfaches Grundgerüst, um ein Programm zu starten. Jetzt müssen wir noch stdin, stdout und stderr im Kind-Prozess umleiten. Dafür werden 3 Pipes benötigt, die vor dem Fork erstellt werden müssen.

int newin[2];
int newout[2];
int newerr[2];

if(pipe(newin) || pipe(newout) || pipe(newerr)) {
    perror("pipe");
    return EXIT_FAILURE;
}

Der Syscall pipe liefert uns 2 File-Deskriptoren, aus dem 1. im Array kann gelesen und in den 2. geschrieben werden. Nach dem Fork stehen die Pipes in beiden Prozessen zur Verfügung. Was jetzt noch fehlt ist, dass im Kind-Prozess die Filedeskriptoren für stdin, stdout und stderr auf die Pipe-Filedeskriptoren zeigen. Dies erledigen wir mit dup2. Dies kopiert unsere Pipe-Filedeskriptoren und ersetzt damit stdin, stdout und stderr im Kind-Prozess.

// child

// we need stdin, stdout and stderr refer to the previously
// created pipes
if(dup2(newin[0], STDIN_FILENO) == -1) {
    perror("dup2");
    return EXIT_FAILURE;
}
if(dup2(newout[1], STDOUT_FILENO) == -1) {
    perror("dup2");
    return EXIT_FAILURE;
}
if(dup2(newerr[1], STDERR_FILENO) == -1) {
    perror("dup2");
    return EXIT_FAILURE;
}

Danach würde im Kind-Prozess ein einfaches printf("Hello World!\n"); in unserer Pipe landen und könnte im Parent aus der Pipe gelesen werden.

Jetzt fehlt nur noch eine Kleinigkeit. Durch fork wurden alle Filedeskriptoren kopiert. Einige davon müssen geschlossen werden, weil sonst das Ende der Streams nicht erreicht werden könnte.

Im Kind-Prozess:

close(newin[1]);

Im Eltern-Prozess:

close(newout[1]);
close(newerr[1]);

Den kompletten Beispielcode gibt es hier. Es wird dort das Programm bc aufgerufen, die Standardeingabe davon befüllt und danach die die Ausgabe des Programms gelesen.

Autor: Olaf | 0 Kommentare | Tags: unix, c

Kommentare


Name
Webseite (optional)
Captcha: 3x=12   x=?
Kommentar