2013-07-07 52 views
5

從我試圖實現的說明這個問題如下:管道輸入/輸出

Linux Pipes as Input and Output

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

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

我的問題是沿問題的線路: Linux Pipes as Input and Output,但更具體。

從本質上講,我試圖取代:

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

通過在C++的管道,以避免使用硬盤驅動器。這裏是我的代碼:

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

我絕對確定上述字符串已正確初始化。但是,有兩件事情對我來說沒有意義:

(1)我正在執行的程序報告「輸入文件爲空」。由於我不是用「<」調用程序,所以不應該期待輸入文件。相反,它應該期待鍵盤輸入。此外,它應該閱讀「gulp_command」中包含的文本。 (2)程序的報告(通過標準輸出提供)出現在終端中。這很奇怪,因爲這個管道的目的是將stdout傳送到我的字符串「receive_output」。但是由於它出現在屏幕上,這表明信息沒有通過管道正確傳遞給變量。如果我落實的if語句結尾以下,

cout << receive_output << endl; 

我什麼也沒得到,彷彿字符串爲空。我感謝您能給我的任何幫助!

編輯:澄清

我的計劃目前使用文本文件的另一個程序進行通信。我的程序寫入一個由外部程序讀取的文本文件(例如input.txt)。該程序然後產生output.txt,這是我的程序讀取的。因此,它是這樣的:

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

因此,我的代碼目前使用,

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

我想要替換使用管道這個過程。我想將我的輸入作爲標準輸入傳遞給程序,並讓我的代碼將該程序的標準輸出讀入字符串。

+0

你的開始主張不明確。你聲明你想用''''''''''''''''''替換'/ directory/program output.txt'來避免文件系統訪問。但是你需要多個流程才能使管道變得明智。 (你可以在一個進程中使用管道,但通常沒有意義。)所以,你可能有'/ directory/program1 output.txt';這是有道理的(你可能以前使用過'/ directory/program1 intermediate.txt;/directory/program2 output.txt')。請澄清你的意圖。 –

+0

好點。我編輯了這個問題。 –

+0

作爲一個額外的澄清,我的目標基本上與您以前回答的問題(stackoverflow.com/questions/1734932/...)中的問題相同(很好的答案)。 –

回答

7

您的主要問題是,你有逆轉的論據dup2()。您需要使用:

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 

我被愚弄到誤讀什麼,直到我把錯誤的設置代碼檢查,並從dup2()調用,這對我的麻煩是什麼讓意外的值,你寫的確定。出現問題時,插入您之前掠過的錯誤檢查。

您也沒有確保從孩子讀取數據的空終止;這段代碼呢。

工作代碼(與診斷),使用cat作爲最簡單的可能 '等命令':

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

輸出示例:

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

請注意,您將需要小心以確保您不會因代碼而死鎖。如果你有一個嚴格的同步協議(所以父母寫一條消息並以鎖步方式讀取響應),你應該沒問題,但是如果父母試圖寫出一條消息太大而不適合管理孩子當孩子試圖寫出一個消息太大而無法放回管道時,那麼每個消息都將被阻塞,等待另一個消息讀取。

+1

這工作很好。感謝您的幫助,以及關於死鎖代碼的提醒。我不認爲我會在這個計劃中遇到這個問題,但很高興知道。 –

+0

如果我想爲兩個子進程執行相同的操作,我們將如何運行此代碼?這意味着在兩個子進程之間發送和接收字符串忽略父進程。 – Mohsin

+1

@Mohsin:它部分取決於孩子們將如何溝通,以及父母過程在啓動兩個孩子後會做什麼。有多種方式來處理它。一種選擇是在執行上述任何代碼之前簡單地使用父分支,並且該子分支的父進程可以退出或等待或繼續進行其他工作,而子進程則如上處理代碼。或者,父進程設置兩個管道,分叉兩次,每個子進程自己排列管道,而父進程關閉兩個管道的四個文件描述符並按照自己的方式進行。 –

1

這聽起來像你正在尋找coprocesses。你可以用C/C++編寫它們,但是由於它們已經在(bash)shell中可用,所以更容易使用shell,對吧?

先用coproc內置啓動外部程序:

coproc external_program 

coproc在後臺啓動該程序並存儲該文件描述符以陣列殼變量與它通信。現在你只需要啓動你的程序將其連接到這些文件描述符:

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

在我的情況下,我的程序反覆調用external_program(數千次),每次輸入不同。這看起來像是在啓動時執行的一次性bash腳本,對吧?我對文件描述符知之甚少,但是如果我要使用它,這是否意味着我必須寫入驅動器上的文件,或者文件描述符能否指向我的代碼中的字符串? –

+1

在這種情況下,文件描述符連接到管道,而不是文件。另外,你也可以運行一個bash腳本數千次。 – 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; 
}