具有输入/输出流的Java流程


90

我下面有以下代码示例。您可以在其中输入命令到bash shell,即echo test让结果回显。但是,先读后。其他输出流不起作用?

为什么会这样或我做错了什么?我的最终目标是创建一个线程计划任务,该任务定期执行对/ bash的命令,因此OutputStreamand InputStream必须串联工作而不停止工作。我也一直在经历错误的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);
}

“管道破损”可能意味着子进程已退出。还没有完全查看其余的代码,以查看其他问题。
vanza 2010年

1
使用单独的线程,它将正常工作
Johnydep 2011年

Answers:


139

首先,我建议更换生产线

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/bashcmd.exe,但是原理应该相同),我发现:

  • 在两行输入后,出现前两个命令的输出,但是程序挂起,
  • 如果我输入,echo test然后输入exit,则该程序将退出第一个循环,因为该cmd.exe进程已退出。然后,程序要求输入另一行(将被忽略),由于子进程已经退出,因此直接跳过了第二个循环,然后退出了自己。
  • 如果我输入,exit然后输入,则会echo test收到IOException,抱怨管道被关闭。这是可以预期的-输入的第一行导致进程退出,并且无处发送第二行。

在我以前使用的程序中,我已经看到了一种可以完成与您想要的功能类似的技巧。该程序保留了许多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;
    }
}

完成此操作后,我可以可靠地运行一些命令,并将每个命令的输出分别返回给我。

echo --EOF--发送到shell的那一行中的两个命令可以确保--EOF--即使在命令出错的情况下也可以终止命令的输出。

当然,这种方法有其局限性。这些限制包括:

  • 如果我输入等待用户输入的命令(例如另一个外壳),则该程序似乎挂起,
  • 假设外壳程序运行的每个进程以换行符结束其输出,
  • 如果shell运行的命令碰巧写了一行,就会感到困惑--EOF--
  • bash报告语法错误,如果输入的文本不匹配,则会退出)

如果您要考虑的是计划任务中要执行的任务,那么这些要点可能对您而言并不重要,因为这些任务将仅限于一个命令或一小部分命令,这些命令或行为永远不会以这种病理方式起作用。

编辑:在Linux上运行此命令后,可以改善退出处理和其他小的更改。


谢谢您的全面答复。但是,我认为我已经找到了解决我问题的真实案例。在您的帖子中引起了我的注意。stackoverflow.com/questions/3645889/…。谢谢。
詹姆斯·摩尔

1
用cmd替换/ bin / bash的行为实际上并不相同,因为我制作的程序执行相同的操作,但是bash存在问题。在mycase中,为每个输入/输出/错误打开三个单独的线程效果最好,而长会话交互式命令则没有任何问题。
Johnydep 2011年


@AlexMills:您的第二条评论是否表示您已解决问题?在您的第一条评论中,没有足够的细节可以说出问题所在,也不是为什么您“突然”获得例外。
卢克·伍德沃德

@Luke,我在您的评论上方提供的链接解决了我的问题
Alexander Mills

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

使您的作者成为最终作者,否则内部类将无法访问它。


1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.