2010-09-04 130 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); 
} 

上面让你的作家作为最终否则将无法通过内部类访问。