2013-07-07 1 views
5

Questa domanda deriva dal mio tentativo di attuare le istruzioni in:tubazioni per l'input/output

Linux Pipes as Input and Output

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

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

La mia domanda è sulla falsariga della questione in: Linux Pipes as Input and Output, ma più specifico.

In sostanza, sto cercando di sostituire:

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

utilizzando tubi in C++, al fine di evitare di utilizzare il disco rigido. Ecco il mio codice:

//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 
} 

Sono assolutamente sicuro che le stringhe di cui sopra sono inizializzate correttamente. Tuttavia, due cose che non hanno senso per me:

(1) Il programma che sto eseguendo segnala che "il file di input è vuoto". Poiché non sto chiamando il programma con "<", non dovrebbe aspettarsi un file di input. Invece, dovrebbe aspettarsi l'input da tastiera. Inoltre, dovrebbe leggere il testo contenuto in "gulp_command".

(2) Il rapporto del programma (fornito tramite uscita standard) viene visualizzato nel terminale. Questo è strano perché lo scopo di questo piping è di trasferire stdout alla mia stringa "receive_output". Ma dal momento che appare sullo schermo, questo mi indica che l'informazione non viene passata correttamente attraverso la pipe alla variabile. Se implemento quanto segue alla fine dell'istruzione if,

cout << receive_output << endl; 

Non ottengo nulla, come se la stringa fosse vuota. Apprezzo qualsiasi aiuto tu possa darmi!

EDIT: Precisazione

Il mio programma attualmente comunica con un altro programma utilizzando i file di testo. Il mio programma scrive un file di testo (ad es. Input.txt), che viene letto dal programma esterno. Quel programma produce quindi output.txt, che viene letto dal mio programma. Quindi è qualcosa di simile:

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

Pertanto, il mio codice utilizza attualmente,

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

voglio sostituire questo processo utilizzando tubi. Voglio passare il mio input come input standard al programma e fare in modo che il mio codice legga lo standard output di quel programma in una stringa.

+0

La tua proposta di partenza non è chiara. Si dichiara di voler sostituire '/ directory/programma output.txt' con qualcosa usando le pipe per evitare l'accesso al file system. Ma hai "bisogno" di più processi per rendere sensato l'uso dei tubi. (Puoi _utilizzare pipe in un unico processo, ma di solito non ha senso.) Quindi potresti avere '/ directory/program1 output.txt'; questo ha senso (e potresti aver già usato '/ directory/program1 intermediate.txt;/directory/program2 output.txt'). Per favore chiarisci la tua intenzione. –

+0

Buon punto. Ho modificato la domanda. –

+0

Come ulteriore chiarimento, il mio obiettivo è fondamentalmente lo stesso della domanda in: stackoverflow.com/questions/1734932/... a cui hai risposto in precedenza (ottima risposta tra l'altro). –

risposta

7

Il tuo problema principale è che avete gli argomenti dup2() invertiti. È necessario utilizzare:

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 

mi sono suckered fraintendimento quello che hai scritto come OK fino a quando ho messo il controllo degli errori sul codice set-up e ottenuto valori imprevisti dalle dup2() chiamate, che mi ha detto quello il problema era. Quando qualcosa va storto, inserisci i controlli di errore ignorati in precedenza.

Inoltre, non ha garantito la cessazione nulla dei dati letti dal bambino; questo codice fa

codice di lavoro (con diagnostica), utilizzando cat come il più semplice possibile 'altro comando': uscita

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

Esempio:

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?)>> 

Nota che sarà necessario fare attenzione per assicurarti di non essere in fase di stallo con il tuo codice. Se hai un protocollo strettamente sincrono (quindi il genitore scrive un messaggio e legge una risposta in lock-step), dovresti stare bene, ma se il genitore sta provando a scrivere un messaggio troppo grande per adattarsi alla pipa al bambino mentre il bambino sta cercando di scrivere un messaggio che è troppo grande per rientrare nella pipa al genitore, allora ognuno di essi verrà bloccato scrivendo mentre aspetta che l'altro lo legga.

+1

Questo ha funzionato magnificamente. Grazie per il tuo aiuto, e per l'idea di un codice in stallo. Non penso che avrò quel problema con questo programma, ma è bello saperlo. –

+0

come dovremmo eseguire questo codice se voglio fare la stessa cosa ma per i processi di due bambini? significa inviare e ricevere stringhe tra due processi figlio ignorando il genitore. – Mohsin

+1

@ Mohsin: dipende in parte da come i bambini comunicheranno e da cosa farà il processo genitore dopo l'avvio dei due figli. Ci sono diversi modi per affrontarlo.Un'opzione è semplicemente avere il fork parent prima di eseguire qualsiasi codice sopra, e il processo genitore da quel fork può uscire o attendere o continuare con altro lavoro, mentre il processo figlio gestisce il codice come sopra. In alternativa, il processo padre imposta le due pipe, i fork due volte e ogni figlio ordina il proprio plumbing mentre il genitore chiude i quattro descrittori di file per i due pipe e va per la sua strada. –

1

Sembra che stiate cercando coprocesses. Puoi programmarli in C/C++ ma dato che sono già disponibili nella shell (bash), più facile usare la shell, giusto?

primo avvio del programma esterno con la coproc incorporato:

coproc external_program 

Il coproc avvia il programma in background e memorizza i descrittori di file per comunicare con esso in una variabile contenitore array.Ora non vi resta che avviare il programma collegandolo a quei descrittori di file:

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

Nel mio caso, il mio programma chiama ripetutamente external_program (migliaia di volte), ogni volta con input diversi. Sembra uno script bash one-shot che viene eseguito al momento del lancio, giusto? Non so molto sui descrittori di file, ma se dovessi usarlo, vuol dire che devo scrivere su un file sull'unità o il descrittore di file può puntare a una stringa nel mio codice? –

+1

I descrittori di file sono collegati alle pipe in questo caso, non ai file. Inoltre, è possibile eseguire uno script bash migliaia di volte. – 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; 
}