2010-09-04 34 views
81

Tôi có ví dụ mã sau đây bên dưới. Nhờ đó, bạn có thể nhập lệnh vào bash shell tức là echo test và có kết quả là echo'd back. Tuy nhiên, sau lần đọc đầu tiên. Các luồng đầu ra khác không hoạt động?Quy trình Java với luồng đầu vào/đầu ra

Tại sao điều này hoặc tôi đang làm điều gì đó sai? Mục tiêu cuối cùng của tôi là tạo một tác vụ được lập lịch Threaded thực hiện một lệnh định kỳ tới/bash để OutputStreamInputStream sẽ phải làm việc song song và không ngừng hoạt động. Tôi cũng đã gặp lỗi java.io.IOException: Broken pipe bất kỳ ý tưởng nào?

Cảm ơn.

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

"Đường ống bị hỏng" có thể có nghĩa là quy trình con đã thoát. Chưa xem hết phần còn lại của mã của bạn để xem các vấn đề khác là gì. – vanza

+1

sử dụng chủ đề riêng biệt, nó sẽ hoạt động tốt – Johnydep

Trả lời

123

Trước hết, tôi muốn giới thiệu thay thế dòng

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

với các dòng

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

ProcessBuilder là mới trong Java 5 và làm cho quá trình hoạt động bên ngoài dễ dàng hơn. Theo tôi, cải tiến quan trọng nhất của nó trên Runtime.getRuntime().exec() là nó cho phép bạn chuyển hướng lỗi tiêu chuẩn của tiến trình con vào đầu ra tiêu chuẩn của nó. Điều này có nghĩa là bạn chỉ có một số InputStream để đọc. Trước đó, bạn cần có hai Chủ đề riêng biệt, một số đọc từ stdout và một đọc từ stderr, để tránh lỗi đệm chuẩn trong khi bộ đệm đầu ra tiêu chuẩn trống (gây ra quá trình con treo), hoặc ngược lại.

Tiếp theo, các vòng (trong đó bạn có hai)

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

chỉ thoát khi reader, mà đọc từ đầu ra tiêu chuẩn của quá trình, trả end-of-file. Điều này chỉ xảy ra khi quá trình thoát. Nó sẽ không trả lại kết thúc của tập tin nếu có xảy ra hiện nay để không có đầu ra nhiều hơn từ quá trình này. Thay vào đó, nó sẽ chờ cho dòng đầu ra tiếp theo từ quá trình và không trả lại cho đến khi nó có dòng tiếp theo này.

Vì bạn đang gửi hai dòng đầu vào cho quy trình trước khi tiếp cận vòng lặp này, đầu tiên của hai vòng này sẽ treo nếu quá trình không thoát sau hai dòng đầu vào này. Nó sẽ ngồi đó chờ đợi một dòng khác để đọc, nhưng sẽ không bao giờ có một dòng khác để đọc.

tôi biên soạn mã nguồn của bạn (Tôi đang trên Windows vào lúc này, vì vậy tôi thay /bin/bash với cmd.exe, nhưng các nguyên tắc cần được như vậy), và tôi thấy rằng:

  • sau khi gõ vào hai dòng, đầu ra từ hai lệnh đầu tiên xuất hiện, nhưng sau đó chương trình bị treo,
  • nếu tôi nhập, giả sử, echo test và sau đó exit, chương trình sẽ thoát khỏi vòng đầu tiên kể từ khi quá trình cmd.exe đã thoát. Chương trình sau đó yêu cầu một dòng đầu vào khác (bị bỏ qua), bỏ qua vòng lặp thứ hai vì quá trình con đã thoát rồi thoát ra khỏi chính nó.
  • nếu tôi nhập exit và sau đó echo test, tôi nhận được một IOException phàn nàn về một đường ống đang bị đóng. Điều này được mong đợi - dòng đầu vào đầu tiên đã khiến quá trình thoát ra và không có nơi nào để gửi dòng thứ hai.

Tôi đã thấy một mẹo làm điều gì đó tương tự như những gì bạn muốn, trong một chương trình tôi từng làm việc. Chương trình này được lưu giữ xung quanh một số shell, chạy các lệnh trong chúng và đọc đầu ra từ các lệnh này. Bí quyết được sử dụng là luôn luôn viết ra một dòng 'ma thuật' đánh dấu kết thúc của đầu ra của lệnh shell, và sử dụng nó để xác định khi nào đầu ra từ lệnh được gửi đến trình bao đã kết thúc.

tôi lấy mã của bạn và tôi đã thay thế tất cả mọi thứ sau khi dòng đó gán cho writer với vòng lặp sau:

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

Sau khi làm điều này, tôi có thể chắc chắn chạy một vài lệnh và có đầu ra từ mỗi trở lại cho riêng tôi.

Hai lệnh echo --EOF-- trong dòng được gửi đến trình bao ở đó để đảm bảo rằng đầu ra từ lệnh được kết thúc bằng --EOF-- ngay cả trong kết quả của lỗi từ lệnh.

Tất nhiên, phương pháp này có những hạn chế của nó. Những hạn chế này bao gồm:

  • nếu tôi nhập một lệnh mà chờ đợi cho người dùng nhập vào (ví dụ vỏ khác), chương trình xuất hiện để treo,
  • Nó giả định rằng mỗi quá trình chạy bằng vỏ kết thúc sản lượng của nó với một dòng mới ,
  • sẽ hơi bị nhầm lẫn nếu lệnh được chạy bởi vỏ xảy ra để viết ra một dòng --EOF--.
  • bash báo cáo lỗi cú pháp và thoát ra nếu bạn nhập một số văn bản với một chưa từng có ).

Những điểm này có thể không quan trọng đối với bạn nếu bạn đang nghĩ đến việc chạy tác vụ được lên lịch sẽ bị giới hạn trong lệnh hoặc tập lệnh nhỏ sẽ không bao giờ hoạt động theo cách bệnh lý như vậy.

EDIT: cải thiện xử lý thoát và các thay đổi nhỏ khác sau khi chạy trên Linux.

+0

Cảm ơn bạn đã trả lời toàn diện Tuy nhiên, tôi nghĩ rằng tôi đã xác định đúng trường hợp cho các vấn đề của tôi. Mang lại sự chú ý của tôi trong bài đăng của bạn. http: // stackoverflow.com/questions/3645889/java-giao dịch-với-con-quá trình. Cảm ơn bạn. –

+1

thay thế/bin/bash với cmd đã không thực sự hành xử giống nhau, như tôi đã thực hiện chương trình mà đã làm như vậy nhưng đã có vấn đề với bash. Trong mycase, mở ba chuỗi riêng biệt cho mỗi đầu vào/đầu ra/err hoạt động tốt nhất mà không gặp bất kỳ vấn đề nào đối với các lệnh tương tác trong phiên dài. – Johnydep

+0

http://stackoverflow.com/questions/14765828/processbuilder-giving-a-file-not-found-exception-when-the-file-does-exist –

1

Bạn có writer.close(); trong mã của mình. Vì vậy, bash nhận EOF trên số stdin và thoát. Sau đó, bạn nhận được Broken pipe khi cố gắng đọc từ số stdout của dấu gạch chéo không còn tồn tại.

4

Tôi nghĩ bạn có thể sử dụng chuỗi như chuỗi chỉ để đọc đầu vào và đầu đọc đầu ra của bạn sẽ nằm trong vòng lặp trong chuỗi chính để bạn có thể đọc và viết cùng một lúc.Bạn có thể sửa đổi chương trình của mình như sau:

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(); 

và bạn có thể đọc sẽ được tương tự như trên tức là

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

làm nhà văn của bạn là cuối cùng nếu không nó wont có thể truy cập bằng lớp bên trong.

Các vấn đề liên quan