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

2022-08-31 10:39:05

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

为什么会这样,或者我做错了什么?我的最终目标是创建一个 Threaded 计划任务,该任务定期执行 /bash 命令,因此 and 必须协同工作,而不是停止工作。我也一直遇到错误有什么想法吗?OutputStreamInputStreamjava.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);
}

答案 1

首先,我建议更换该行

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

与线条

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

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

接下来,循环(其中有两个)

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

仅当 从进程的标准输出读取 的 返回文件末尾时,才退出。仅当进程退出时,才会发生这种情况。如果当前进程不再有输出,则不会返回文件结尾。相反,它将等待进程的下一行输出,并且在它有下一行之前不返回。readerbash

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

我编译了你的源代码(我现在在Windows上,所以我用替换了,但原则应该是一样的),我发现:/bin/bashcmd.exe

  • 键入两行后,将显示前两个命令的输出,但随后程序挂起,
  • 如果我输入,比如说,然后,程序会从进程退出后的第一个循环中退出。然后,程序要求另一行输入(被忽略),由于子进程已经退出,因此直接跳过第二个循环,然后退出自身。echo testexitcmd.exe
  • 如果我输入然后,我得到一个IOException抱怨管道被关闭。这是可以预料到的 - 第一行输入导致进程退出,并且没有地方发送第二行。exitecho test

我见过一个技巧,它可以做一些类似于你似乎想要的东西,在我曾经做过的程序中。该程序保留了许多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上运行此内容后,改进了退出处理和其他小更改。


答案 2

我认为你可以使用像恶魔线程这样的线程来读取你的输入,你的输出读取器将已经在主线程中的同时循环中,这样你就可以同时读取和写入。您可以像这样修改程序:

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

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


推荐