具有输入/输出流的Java进程
我有下面的代码示例。通过该命令,您可以向bash shell输入命令,即具有输入/输出流的Java进程,java,stream,Java,Stream,我有下面的代码示例。通过该命令,您可以向bash shell输入命令,即echo test,并返回结果echo。然而,在第一次阅读之后。其他输出流不工作 为什么是这样,还是我做错了什么?我的最终目标是创建一个线程调度任务,定期执行一个命令来/bash,这样OutputStream和InputStream必须协同工作,而不是停止工作。我还遇到了错误java.io.IOException:breaked pipe有什么想法吗 谢谢 String line; Scanner scan = new Sc
echo test
,并返回结果echo。然而,在第一次阅读之后。其他输出流不工作
为什么是这样,还是我做错了什么?我的最终目标是创建一个线程调度任务,定期执行一个命令来/bash,这样OutputStream
和InputStream
必须协同工作,而不是停止工作。我还遇到了错误java.io.IOException:breaked 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);
}
您有
writer.close()代码>在您的代码中。因此bash在其stdin
上接收EOF并退出。然后,在尝试读取已失效bash的stdout
时,您会发现管道破裂。首先,我建议更换管道
Process process = Runtime.getRuntime ().exec ("/bin/bash");
用台词
ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();
ProcessBuilder在Java5中是新的,它使运行外部进程更容易。在我看来,与Runtime.getRuntime().exec()
相比,它最显著的改进是允许您将子进程的标准错误重定向到其标准输出中。这意味着您只有一个要读取的InputStream
。在此之前,您需要有两个单独的线程,一个从stdout
读取,另一个从stderr
读取,以避免在标准输出缓冲区为空时填充标准错误缓冲区(导致子进程挂起),反之亦然
接下来是循环(其中有两个)
仅当从进程的标准输出读取的读取器
返回文件结尾时退出。这仅在bash
进程退出时发生。如果进程当前没有更多输出,它将不会返回文件结尾。相反,它将等待进程的下一行输出,直到有了下一行才返回
由于您在到达此循环之前向进程发送了两行输入,因此如果进程在这两行输入之后没有退出,则这两个循环中的第一行将挂起。它会坐在那里等待另一行被阅读,但永远不会有另一行让它阅读
我编译了你的源代码(我现在在Windows上,所以我用cmd.exe
替换了/bin/bash
,但原理应该是一样的),我发现:
- 键入两行后,前两个命令的输出会出现,但程序会挂起
- 如果我输入,比如说,
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),程序将显示为挂起
- 它假设shell运行的每个进程都以换行符结束其输出
- 如果shell运行的命令恰好写出了一行
--EOF--
,它会有点混乱
bash
报告语法错误,如果您输入的某些文本中包含不匹配的)
,则会退出
如果您正在考虑作为计划任务运行的内容仅限于一个命令或一小部分命令,而这些命令永远不会以这种病态的方式运行,那么这些要点对您可能并不重要
编辑:在Linux上运行此程序后,改进退出处理和其他小更改。我认为您可以使用类似于demon线程的线程来读取输入,并且您的输出读取器将已经在主线程中的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);
}
将您的编写器设置为最终版本,否则内部类将无法访问它 “断管”可能表示子进程已退出。我还没有完全查看代码的其余部分以了解其他问题。请使用单独的线程,它会很好地工作。谢谢您的全面回答。但是,我认为我已经确定了我的问题的真实情况。在你的帖子中引起我的注意。谢谢。用cmd替换/bin/bash的行为并不完全相同,因为我制作的程序也有相同的行为,但bash有问题。在mycase中,为每个输入/输出/错误打开三个独立的线程对于长会话交互命令来说效果最好,没有任何问题。@AlexMills:你的第二条评论是否意味着你已经解决了问题?这远远不够
while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}