【问题标题】:Java Process with Input/Output Stream具有输入/输出流的 Java 进程
【发布时间】:2011-04-08 07:41:37
【问题描述】:

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

为什么会这样或者我做错了什么?我的最终目标是创建一个线程计划任务,该任务定期向 /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);
}

【问题讨论】:

  • "Broken pipe" 可能意味着子进程已经退出。尚未完全查看您的其余代码以了解其他问题。
  • 使用单独的线程,它会工作得很好

标签: java stream


【解决方案1】:

首先,我建议更换线路

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 上运行后改进退出处理和其他小改动。

【讨论】:

  • 感谢您的全面回答但是,我想我已经确定了我的问题的真实情况。在你的帖子中引起了我的注意。 stackoverflow.com/questions/3645889/…。谢谢。
  • 用 cmd 替换 /bin/bash 的行为实际上并不相同,因为我制作的程序执行相同但 bash 有问题。在我的情况下,为每个输入/输出/错误打开三个单独的线程效果最好,对于长会话交互式命令没有任何问题。
  • @AlexMills:您的第二条评论是否意味着您已经解决了问题?您的第一条评论中没有足够详细的信息来说明问题所在,也没有说明您“突然”遇到异常的原因。
  • @Luke,我在您的评论上方提供的链接解决了我的问题
【解决方案2】:

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

将您的作家设为 final,否则内部类将无法访问它。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2016-01-29
      • 1970-01-01
      • 1970-01-01
      • 2012-03-21
      • 1970-01-01
      • 2012-07-19
      • 2012-11-17
      相关资源
      最近更新 更多