2013-07-07 20 views
5

Diese Frage von meinem Versuch, folgt den Anweisungen in implementieren:Piping für die Eingabe/Ausgabe-

Linux Pipes as Input and Output

How to send a simple string between two programs using pipes?

http://tldp.org/LDP/lpg/node11.html

Meine Frage nach dem Vorbild der Frage ist in: Linux Pipes as Input and Output, aber spezifischer.

Im Grunde versuche ich, zu ersetzen:

/directory/program <input.txt> output.txt 

mit Rohren in C++, um mit der Festplatte zu vermeiden. Hier ist mein Code:

//LET THE PLUMBING BEGIN 
int fd_p2c[2], fd_pFc[2], bytes_read; 
    // "p2c" = pipe_to_child, "pFc" = pipe_from_child (see above link) 
pid_t childpid; 
char readbuffer[80]; 
string program_name;// <---- includes program name + full path 
string gulp_command;// <---- includes my line-by-line stdin for program execution 
string receive_output = ""; 

pipe(fd_p2c);//create pipe-to-child 
pipe(fd_pFc);//create pipe-from-child 
childpid = fork();//create fork 

if (childpid < 0) 
{ 
    cout << "Fork failed" << endl; 
    exit(-1); 
} 
else if (childpid == 0) 
{ 
    dup2(0,fd_p2c[0]);//close stdout & make read end of p2c into stdout 
    close(fd_p2c[0]);//close read end of p2c 
    close(fd_p2c[1]);//close write end of p2c 
    dup2(1,fd_pFc[1]);//close stdin & make read end of pFc into stdin 
    close(fd_pFc[1]);//close write end of pFc 
    close(fd_pFc[0]);//close read end of pFc 

    //Execute the required program 
    execl(program_name.c_str(),program_name.c_str(),(char *) 0); 
    exit(0); 
} 
else 
{ 
    close(fd_p2c[0]);//close read end of p2c 
    close(fd_pFc[1]);//close write end of pFc 

    //"Loop" - send all data to child on write end of p2c 
    write(fd_p2c[1], gulp_command.c_str(), (strlen(gulp_command.c_str()))); 
    close(fd_p2c[1]);//close write end of p2c 

    //Loop - receive all data to child on read end of pFc 
    while (1) 
    {   
     bytes_read = read(fd_pFc[0], readbuffer, sizeof(readbuffer)); 

     if (bytes_read <= 0)//if nothing read from buffer... 
      break;//...break loop 

     receive_output += readbuffer;//append data to string 
    } 
    close(fd_pFc[0]);//close read end of pFc 
} 

Ich bin absolut sicher, dass die oben genannten Zeichenfolgen ordnungsgemäß initialisiert werden. Allerdings passieren zwei Dinge, die für mich keinen Sinn ergeben:

(1) Das Programm, das ich ausführe meldet, dass die "Eingabedatei leer ist." Da ich das Programm nicht mit "<" anrufe, sollte es keine Eingabedatei erwarten. Stattdessen sollte Tastatureingabe erwartet werden. Außerdem sollte es den Text lesen, der in "gulp_command" enthalten ist.

(2) Der Bericht des Programms (über die Standardausgabe bereitgestellt) wird im Terminal angezeigt. Das ist seltsam, weil der Zweck dieses Pipings darin besteht, stdout in meine Zeichenfolge "receive_output" zu übertragen. Aber da es auf dem Bildschirm erscheint, zeigt mir das an, dass die Information nicht korrekt durch die Pipe an die Variable übergeben wird. Wenn ich Folgendes am Ende der if-Anweisung implementiere:

Ich bekomme nichts, als ob die Zeichenfolge leer ist. Ich schätze jede Hilfe, die Sie mir geben können!

EDIT: Klarstellung

Mein Programm in Verbindung steht zur Zeit mit einem anderen Programm mit Textdateien. Mein Programm schreibt eine Textdatei (z. B. input.txt), die vom externen Programm gelesen wird. Dieses Programm erzeugt dann output.txt, das von meinem Programm gelesen wird. Es ist also so etwas wie dieses:

my code -> input.txt -> program -> output.txt -> my code 

Daher mein Code verwendet derzeit,

system("program <input.txt> output.txt"); 

Ich möchte diesen Prozess ersetzen Verwendung Rohren. Ich möchte meine Eingabe als Standardeingabe an das Programm übergeben und meinen Code die Standardausgabe von diesem Programm in eine Zeichenfolge lesen lassen.

+0

Ihr Ausgangsvorschlag ist nicht klar. Sie geben an, dass Sie "/ verzeichnis/program output.txt" durch etwas ersetzen möchten, das Pipes verwendet, um Dateisystemzugriff zu vermeiden. Aber Sie brauchen mehrere Prozesse, um die Verwendung von Rohren sinnvoll zu machen. (Sie können Pipes in einem einzigen Prozess verwenden, aber das macht normalerweise keinen Sinn.) Sie könnten also '/ directory/program1 ​​Ausgabe.txt'; das macht Sinn (und Sie könnten zuvor '/ directory/program1 intermediate.txt;/directory/program2 output.txt') verwendet haben. Bitte klären Sie Ihre Absicht. –

+0

Guter Punkt. Ich habe die Frage bearbeitet. –

+0

Als zusätzliche Erklärung, mein Ziel ist im Grunde das gleiche wie die Frage in: stackoverflow.com/questions/1734932/..., die Sie zuvor beantwortet haben (große Antwort übrigens). –

Antwort

7

Ihr Hauptproblem ist, dass Sie die Argumente dup2() rückgängig gemacht haben. Sie müssen verwenden:

dup2(fd_p2c[0], 0); // Duplicate read end of pipe to standard input 
dup2(fd_pFc[1], 1); // Duplicate write end of pipe to standard output 

habe ich suckered in Verlesen, was Sie als OK geschrieben, bis ich Fehler am Set-up-Code Überprüfung gestellt und bekam unerwartete Werte aus den dup2() Anrufe, die mir erzählte, was das Problem war. Wenn etwas schief geht, fügen Sie die zuvor übersprungenen Fehlerprüfungen ein.

Sie haben auch keine Null-Beendigung der vom Kind gelesenen Daten sichergestellt; Dieser Code tut es.

Arbeitscode (mit Diagnose), mit cat als einfachste 'anderen Befehl':

#include <unistd.h> 
#include <string> 
#include <iostream> 
using namespace std; 

int main() 
{ 
    int fd_p2c[2], fd_c2p[2], bytes_read; 
    pid_t childpid; 
    char readbuffer[80]; 
    string program_name = "/bin/cat"; 
    string gulp_command = "this is the command data sent to the child cat (kitten?)"; 
    string receive_output = ""; 

    if (pipe(fd_p2c) != 0 || pipe(fd_c2p) != 0) 
    { 
     cerr << "Failed to pipe\n"; 
     exit(1); 
    } 
    childpid = fork(); 

    if (childpid < 0) 
    { 
     cout << "Fork failed" << endl; 
     exit(-1); 
    } 
    else if (childpid == 0) 
    { 
     if (dup2(fd_p2c[0], 0) != 0 || 
      close(fd_p2c[0]) != 0 || 
      close(fd_p2c[1]) != 0) 
     { 
      cerr << "Child: failed to set up standard input\n"; 
      exit(1); 
     } 
     if (dup2(fd_c2p[1], 1) != 1 || 
      close(fd_c2p[1]) != 0 || 
      close(fd_c2p[0]) != 0) 
     { 
      cerr << "Child: failed to set up standard output\n"; 
      exit(1); 
     } 

     execl(program_name.c_str(), program_name.c_str(), (char *) 0); 
     cerr << "Failed to execute " << program_name << endl; 
     exit(1); 
    } 
    else 
    { 
     close(fd_p2c[0]); 
     close(fd_c2p[1]); 

     cout << "Writing to child: <<" << gulp_command << ">>" << endl; 
     int nbytes = gulp_command.length(); 
     if (write(fd_p2c[1], gulp_command.c_str(), nbytes) != nbytes) 
     { 
      cerr << "Parent: short write to child\n"; 
      exit(1); 
     } 
     close(fd_p2c[1]); 

     while (1) 
     { 
      bytes_read = read(fd_c2p[0], readbuffer, sizeof(readbuffer)-1); 

      if (bytes_read <= 0) 
       break; 

      readbuffer[bytes_read] = '\0'; 
      receive_output += readbuffer; 
     } 
     close(fd_c2p[0]); 
     cout << "From child: <<" << receive_output << ">>" << endl; 
    } 
    return 0; 
} 

Beispielausgabe:

Writing to child: <<this is the command data sent to the child cat (kitten?)>> 
From child: <<this is the command data sent to the child cat (kitten?)>> 

Beachten Sie, dass Sie benötigen, um vorsichtig sein, um sicherzustellen, dass Sie nicht mit Ihrem Code blockiert werden. Wenn Sie ein strikt synchrones Protokoll haben (also schreibt der Elternteil eine Nachricht und liest eine Antwort in lock-step), sollte es Ihnen gut gehen, aber wenn der Elternteil versucht, eine Nachricht zu schreiben, die zu groß ist, um in die Pipe zu passen Während das Kind versucht, eine Nachricht zu schreiben, die zu groß ist, um in die Pipe zurück zu dem Elternteil zu passen, wird jedes Schreiben blockiert, während es auf das Lesen des anderen wartet.

+1

Das hat wunderbar funktioniert. Vielen Dank für Ihre Hilfe und für die head-ups über festgefahrenen Code. Ich glaube nicht, dass ich dieses Problem mit diesem Programm haben werde, aber es ist gut zu wissen. –

+0

Wie würden wir diesen Code ausführen, wenn ich dasselbe für zwei Kinderprozesse machen möchte? Bezeichnet das Senden und Empfangen von Zeichenfolgen zwischen zwei untergeordneten Prozessen, wobei das übergeordnete Element ignoriert wird. – Mohsin

+1

@Mohsin: Es hängt teilweise davon ab, wie die Kinder kommunizieren und was der Elternprozess nach dem Start der beiden Kinder tun wird. Es gibt mehrere Möglichkeiten, damit umzugehen.Eine Option besteht darin, einfach den übergeordneten Fork zu verwenden, bevor Sie einen der obigen Codes ausführen, und der übergeordnete Prozess von diesem Fork kann beendet werden oder warten oder mit anderen Arbeiten fortfahren, während der untergeordnete Prozess den Code wie oben behandelt. Alternativ richtet der Elternprozess die beiden Pipes ein, verzweigt sich zweimal, und jedes Kind sortiert seine eigenen Leitungen aus, während der Elternteil die vier Dateideskriptoren für die beiden Pipes schließt und seinen eigenen Weg geht. –

1

Es klingt, als ob Sie nach coprocesses suchen. Sie können sie in C/C++ programmieren, aber da sie bereits in der (bash) Shell verfügbar sind, ist es einfacher, die Shell zu verwenden, richtig?

Zuerst startet das Programm mit dem externen coproc builtin:

coproc external_program 

Die coproc startet das Programm im Hintergrund und speichert die Datei-Deskriptoren mit ihm in einem Array Shell-Variable zu kommunizieren.Jetzt müssen Sie nur Ihr Programm starten, sie auf diese Filedeskriptoren verbindet:

your_program <&${COPROC[0]} >&${COPROC[1]} 
+0

In meinem Fall ruft mein Programm wiederholt external_program (tausendmal) auf, jedes Mal mit anderen Eingaben. Das scheint wie ein One-Shot-Bash-Skript zu sein, das beim Start ausgeführt wird, oder? Ich weiß nicht viel über Dateideskriptoren, aber wenn ich das verwenden würde, bedeutet das, dass ich in eine Datei auf dem Laufwerk schreiben muss, oder kann der Dateideskriptor auf eine Zeichenfolge in meinem Code zeigen? –

+1

Die Dateideskriptoren sind in diesem Fall mit Pipes verbunden, keine Dateien. Außerdem können Sie ein Bash-Skript auch tausende Male ausführen. – Joni

1
#include <stdio.h> 
#include <unistd.h> 
#include <sys/stat.h> 
#include <sys/wait.h> 
#include <fcntl.h> 
#include <string.h> 
#include <iostream> 
using namespace std; 
int main() { 
    int i, status, len; 
    char str[10]; 
    mknod("pipe", S_IFIFO | S_IRUSR | S_IWUSR, 0); //create named pipe 
    pid_t pid = fork(); // create new process 
    /* Process A */ 
    if (pid == 0) { 
     int myPipe = open("pipe", O_WRONLY); // returns a file descriptor for the pipe 
     cout << "\nThis is process A having PID= " << getpid(); //Get pid of process A 
     cout << "\nEnter the string: "; 
     cin >> str; 
     len = strlen(str); 
     write(myPipe, str, len); //Process A write to the named pipe 
     cout << "Process A sent " << str; 
     close(myPipe); //closes the file descriptor fields. 
     } 
    /* Process B */ 
     else { 
     int myPipe = open("pipe", O_RDONLY); //Open the pipe and returns file descriptor 
     char buffer[21]; 
     int pid_child; 
     pid_child = wait(&status); //wait until any one child process terminates 
     int length = read(myPipe, buffer, 20); //reads up to size bytes from pipe with descriptor fields, store results 
    // in buffer; 
     cout<< "\n\nThis is process B having PID= " << getpid();//Get pid of process B 
     buffer[length] = '\0'; 
     cout << "\nProcess B received " << buffer; 
     i = 0; 
     //Reverse the string 
     for (length = length - 1; length >= 0; length--) 
     str[i++] = buffer[length]; 
     str[i] = '\0'; 
     cout << "\nRevers of string is " << str; 
     close(myPipe); 
     } 
    unlink("pipe"); 
return 0; 
}