Frage #2

Wie kann ich die Ausgabe eines externen Prozesses auffangen und verarbeiten?

Antwort:

Oft will man ein externes Programm aus einem selbst geschriebenen aufrufen. Dies ist soweit kein Problem, dafür gibt es Funktionen wie system(). system() schreibt jedoch die Ausgabe des gerufenen Programms in die Standardausgabe unseres Programm und das ohne, dass wir die Daten bearbeiten oder abspeichern können.
Mit Hilfe der im Linux-Bereich üblichen Eingabe/Ausgabe-Weiterleitung können wir zwar die Ausgabe in eine Datei schreiben, jedoch geht dann auch die Ausgabe unseres Programms verloren.
Abhilfe schafft hier popen(). Mit dieser Funktion können wir eine Pipe zu einem anderen Programm erstellen. Eine Pipe ist eine unidirektionale Verbindung, das heißt wir können entweder lesen oder schreiben, aber nicht beides gleichzeitig. Falls uns nur die Ausgabe des externen Programms interessiert und wir keinen weiteren Einfluss darauf nehmen wollen haben wir hier bereits die passende Lösung. Hier ein Beispiel, dass alle im Verzeichnis enthaltenen Dateien mit dem Konsolenbefehl „ls“ ausgibt:

#include <stdio.h>
#include <ctype.h>
 
int main()
{
  FILE *p = popen( "ls", "r" );      // Pipe zu "ls" öffnen
  char c;
 
  if( p == NULL )                    // Prüfen, ob beim Öffnen der Pipe etwas schief gegangen ist
  {
    printf( "Pipe konnte nicht geoeffnet werden.\n" );
    return 1;
  }
 
  while( feof( p ) == 0 )            // Solange Zeichen da sind lesen
  {
    c = fgetc( p );                  // Ein Zeichen aus der Pipe lesen
    if( isprint( c ) != 0 || isspace( c ) != 0 )  // Prüfen, ob wir das Zeichen ausgeben können
      putchar( c );                  // Zeichen ausgeben
  }
  putchar( '\n' );
 
  return 0;
}

Wir könnten natürlich die einzelnen Zeichen interpretieren und in unserem Programm verwenden, hier soll aber nur gezeigt werden wie man überhaupt an die Ausgabe kommt.
Aber was nun wenn wir gleichzeitig Informationen über die Pipe schicken und empfangen wollen? Dieses Problem können wir lösen indem wir unser Programm ein zweites Mal starten. Das erste Mal öffnet sich das Programm selbst über eine Pipe zum Lesen. Erst in der zweiten Instanz wird das externe Programm über eine Pipe zum Schreiben gestartet. Dadurch geht die komplette Ausgabe aller Prozesse an die erste Instanz unseres eigenen Programms. Mittels Kommandozeilenparameter können wir unterscheiden, ob wir uns in der ersten oder zweiten Instanz unseres Programms befinden. Hier ein Beispiel in dem zuerst eine Shell geöffnet wird und dann alle darin enthaltenen Dateien gelistet werden:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
 
int main( int argc, char *argv[] )
{
  if( argc == 1 )      // Wenn diese Bedingung wahr ist, sind wir in der 1. Instanz
  {
    // Speicher für Aufruf mit Kommandozeilenparameter "-copy" reservieren
    char *path = (char *) malloc( ( strlen( argv[0] ) + strlen( " -copy" ) + 1 ) * sizeof( char ) );
    char c;
    strcpy( path, argv[0] );          // Pfad zum Programm kopieren
    strcat( path, " -copy" );         // Parameter dazuhängen
    FILE *copy = popen( path, "r" );  // 2. Instanz erstellen
    while( feof( copy ) == 0 )        // Selbiges wie oben
    {
      c = fgetc( copy );
      if( isprint( c ) != 0 || isspace( c ) != 0 )
        putchar ( c );
    }
    putchar( '\n' );
    pclose (copy);                     // 2. Instanz beenden
    free (path);  
  }
  else if( strcmp( argv[1], "-copy" ) == 0 )  // Wir sind in der 2. Instanz
  {
    FILE *shell = popen( "sh", "w" );       // Shell öffnen
    fprintf( shell, "ls" );                 // Befehl "ls" in der Shell ausführen
    pclose( shell );                        // Shell beenden
  }
 
  return 0;
}

Nun können wir sowohl Informationen an das externe Programm senden als auch empfangen. Eine etwas flexiblere Lösung bietet das Qt-Framework.