带输入/输出流的Java进程

时间:2010-09-04 20:43:23

标签: java stream

我在下面有以下代码示例。因此,您可以向bash shell输入命令,即echo test,并将结果回显。但是,在第一次阅读之后。其他输出流不起作用?

为什么这样或我做错了什么?我的最终目标是创建一个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);
}

3 个答案:

答案 0 :(得分:131)

首先,我建议更换一行

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

带有

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

ProcessBuilder是Java 5中的新增功能,可以更轻松地运行外部进程。在我看来,它比Runtime.getRuntime().exec()最显着的改进是它允许您将子进程的标准错误重定向到其标准输出。这意味着您只能有一个InputStream来读取。在此之前,您需要有两个单独的线程,一个从stdout读取,一个从stderr读取,以避免在标准输出缓冲区为空时填充标准错误缓冲区(导致子进程挂起) ),反之亦然。

接下来,循环(你有两个)

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

仅在从进程的标准输出读取的reader返回文件结尾时才退出。这仅在bash进程退出时发生。如果目前没有更多来自进程的输出,它将不会返回文件结尾。相反,它将等待进程的下一行输出,直到它有下一行才会返回。

由于您在到达此循环之前向进程发送了两行输入,因此如果在这两行输入之后进程尚未退出,则这两个循环中的第一个将挂起。它将坐在那里等待读取另一行,但是它永远不会有另一行读它。

我编译了你的源代码(我现在在Windows上,所以我用/bin/bash替换cmd.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),程序似乎挂起,
  • 它假定shell运行的每个进程都以换行符结束其输出
  • 如果shell运行的命令恰好写出一行--EOF--,则会有点混淆。
  • bash报告语法错误,如果您输入的文字不匹配),则会退出。

如果您想要运行的任何计划任务将被限制为命令或一组永远不会以这种病态行事的命令,这些点对您来说可能无关紧要。

编辑:在Linux上运行此功能后,改进退出处理和其他微小更改。

答案 1 :(得分:4)

我认为您可以使用类似于线程的线程来读取您的输入,并且您的输出读取器已经在主线程中的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);
}

让你的作家成为最终的,否则它将无法通过内部类来访问。

答案 2 :(得分:1)

您的代码中有writer.close();。所以bash在stdin上收到EOF并退出。然后,当您尝试从已解散的bash的Broken pipe中读取时,您会获得stdout