2010-09-04 111 views
81

我在下面有下面的代碼示例。因此,您可以向bash shell輸入一條命令,即echo test並返回結果。但是,在第一次閱讀之後。其他輸出流不起作用?帶輸入/輸出流的Java進程

爲什麼這樣做還是我做錯了什麼?我的最終目標是創建一個Threaded計劃任務,定期執行一個命令到/ bash,因此OutputStreamInputStream必須協同工作,而不是停止工作。我也遇到過錯誤java.io.IOException: Broken pipe有什麼想法?

謝謝。

String line; 
Scanner scan = new Scanner(System.in); 

Process process = Runtime.getRuntime().exec ("/bin/bash"); 
OutputStream stdin = process.getOutputStream(); 
InputStream stderr = process.getErrorStream(); 
InputStream stdout = process.getInputStream(); 

BufferedReader reader = new BufferedReader (new InputStreamReader(stdout)); 
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin)); 

String input = scan.nextLine(); 
input += "\n"; 
writer.write(input); 
writer.flush(); 

input = scan.nextLine(); 
input += "\n"; 
writer.write(input); 
writer.flush(); 

while ((line = reader.readLine()) != null) { 
System.out.println ("Stdout: " + line); 
} 

input = scan.nextLine(); 
input += "\n"; 
writer.write(input); 
writer.close(); 

while ((line = reader.readLine()) != null) { 
System.out.println ("Stdout: " + line); 
} 
+0

「破管」可能意味着子進程已退出。沒有完全查看剩下的代碼來查看其他問題。 – vanza 2010-09-04 20:59:49

+1

使用 單獨的線程,它將工作得很好 – Johnydep 2011-12-08 11:07:03

回答

123

首先,我會建議與線

ProcessBuilder builder = new ProcessBuilder("/bin/bash"); 
builder.redirectErrorStream(true); 
Process process = builder.start(); 

的ProcessBuilder是在Java 5中新的替代線路

Process process = Runtime.getRuntime().exec ("/bin/bash"); 

和使運行外部流程更容易。在我看來,它比Runtime.getRuntime().exec()最顯着的改進是它允許你將子進程的標準錯誤重定向到它的標準輸出。這意味着您只有一個InputStream可供閱讀。在此之前,您需要有兩個獨立的線程,一個從stdout讀取一個讀數,另一個從stderr讀取,以避免在標準輸出緩衝區爲空(導致子進程掛起)時標準錯誤緩衝區填充,反之亦然。

接下來,循環(其中有兩個)

while ((line = reader.readLine()) != null) { 
    System.out.println ("Stdout: " + line); 
} 

只有當reader,從過程的標準輸出讀取,返回檔案結尾退出。只有當進程退出時纔會發生這種情況。如果目前發生的情況是不能從過程輸出,它將不會返回文件結束。相反,它將等待進程的下一行輸出,並且不會返回,直到它具有此下一行。

由於您在到達此循環之前將兩行輸入發送到進程,因此如果在這兩行輸入後進程沒有退出,則這兩個循環中的第一個將掛起。它會坐在那裏等待另一條線被閱讀,但是永遠不會有另一條線被閱讀。

我編譯你的源代碼(我在Windows上的那一刻,讓我代替/bin/bashcmd.exe,但原則應該是相同的),我發現:

  • 在兩次打字後行,前兩個命令的輸出會出現,但程序會掛起,如果我輸入了,例如echo test,然後exit,程序就會從cmd.exe進程退出以來使它不在第一個循環中。程序然後要求輸入另一行(被忽略),直接跳過第二個循環,因爲子進程已經退出,然後退出。
  • 如果我輸入exit然後echo test,我得到一個IOException,抱怨關閉了一個管道。這是可以預料的 - 第一行輸入導致進程退出,無處可送第二行。

我已經看到了一個類似於你似乎想要的東西,在我曾經工作的程序中使用的技巧。這個程序圍繞着許多shell,在其中運行命令並從這些命令中讀取輸出。使用的技巧是總是寫出一個標記shell命令輸出結束的'魔術'行,並用它來確定發送到shell的命令的輸出何時完成。

我把你的代碼,我取代了分配給writer與下面的循環線後一切:

while (scan.hasNext()) { 
    String input = scan.nextLine(); 
    if (input.trim().equals("exit")) { 
     // Putting 'exit' amongst the echo --EOF--s below doesn't work. 
     writer.write("exit\n"); 
    } else { 
     writer.write("((" + input + ") && echo --EOF--) || echo --EOF--\n"); 
    } 
    writer.flush(); 

    line = reader.readLine(); 
    while (line != null && ! line.trim().equals("--EOF--")) { 
     System.out.println ("Stdout: " + line); 
     line = reader.readLine(); 
    } 
    if (line == null) { 
     break; 
    } 
} 

這樣做後,我可以可靠地運行一些命令並有從每個回來的輸出給我個人。

發送到shell的行中的兩條命令echo --EOF--用於確保命令的輸出以--EOF--結束,即使命令錯誤的結果也是如此。

當然,這種方法有其侷限性。這些限制包括:

  • 如果我輸入等待用戶輸入的命令(例如另一外殼),則程序似乎掛起,
  • 它假定每個由外殼程序運行處理結束其輸出與一個換行,
  • 如果shell運行的命令碰巧寫出一行--EOF--,它會有點困惑。
  • bash報告語法錯誤,並在您輸入一些文字不匹配的)時退出。

如果無論你是否想要作爲計劃任務運行,這些點可能並不重要,它將限制在一個命令或一組永遠不會以這種病態方式運行的命令。

編輯:在Linux上運行此操作之後,改進退出處理和其他次要更改。

+0

謝謝你的全面答案然而,我想我已經確定了我的問題的真實案例。在你的帖子中引起了我的注意。 HTTP://計算器。COM /問題/ 3645889/Java的交易,與孩子過程。謝謝。 – 2010-09-05 11:24:31

+1

用cmd替換/ bin/bash的行爲並不一樣,因爲我製作的程序雖然做了相同的但是有bash的問題。 在mycase中,爲每個輸入/輸出/ err打開三個單獨的線程對長會話交互命令沒有任何問題。 – Johnydep 2011-12-08 10:55:11

+0

http://stackoverflow.com/questions/14765828/processbuilder-giving-a-file-not-found-exception-when-the-file-does-exist – 2013-10-07 23:25:15

1

您在代碼中有writer.close();。所以bash收到EOF的stdin並退出。然後當您嘗試從stdout讀取已停用的bash時獲得Broken pipe

4

我認爲你可以使用像demon-thread這樣的線程來讀取你的輸入,而你的輸出閱讀器已經在主線程中的while循環中,這樣你就可以同時讀寫。你可以像這樣修改你的程序:

Thread T=new Thread(new Runnable() { 

    @Override 
    public void run() { 
     while(true) 
     { 
      String input = scan.nextLine(); 
      input += "\n"; 
      try { 
       writer.write(input); 
       writer.flush(); 
      } catch (IOException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 

     } 

    } 
}); 
T.start(); 

,你可以閱讀將是一樣的,即

while ((line = reader.readLine()) != null) { 
    System.out.println ("Stdout: " + line); 
} 

上面讓你的作家作爲最終否則將無法通過內部類訪問。