【问题标题】:Execute a command in Java without redirecting the output在 Java 中执行命令而不重定向输出
【发布时间】:2014-03-13 00:13:18
【问题描述】:

如何从 Java 程序运行外部命令(通过 shell),这样就不会发生重定向,并等待命令结束?我希望外部程序的文件描述符与 Java 程序的文件描述符相同。特别是 我不希望将输出重定向到 Java 程序正在读取的管道。让 Java 程序中继输出不是解决方案。

这意味着对java.lang.Runtime.exec 的简单调用不是解决方案。我假设涉及java.lang.ProcessBuilder,但是如何指定输出和错误流必须与调用Java进程相同?

class A {
    public static void main(String[] args) {
        try {
            ProcessBuilder pb = new ProcessBuilder("echo", "foo");
            /*TODO: pb.out = System.out; pb.err = System.err;*/
            Process p = pb.start();
            p.waitFor();
        } catch (Exception e) {
            System.err.println(e);
            System.exit(1);
        }
    }
}

(这可能是正确的方法,也可能不是。)

换句话说,我正在寻找 Java 的 system,但我只能找到(大致)popen

下面是一个中继不能工作的例子:如果子进程同时写入stdout和stderr并且Java程序正在中继,那么Java程序无法知道子进程中write调用的顺序.因此,如果两个流最终位于同一个文件中,那么 Java 程序在 stdout 和 stderr 上的输出顺序将明显不同。混合 stdout 和 stderr 当然不是解决方案,因为调用者可能希望将它们分开。

虽然我认为这个问题引起了人们普遍的兴趣,但特定于 Linux 的解决方案可以解决我的直接问题。

【问题讨论】:

  • 你应该使用Process.getInputStream()。您需要在两个单独的线程中从 sysout 和 err 读取进程,否则它们的缓冲区可能会填满并且进程将阻塞。 This 是一篇关于从 Java 运行外部进程的优秀文章。就我个人而言,我会使用Apache commons exec 来避免自己的痛苦。
  • @BoristheSpider 我不想从 Java 程序中读取子进程的输出。
  • 如果您不想中继输出,并且满足于坚持特定于平台的解决方案,您可以使用 jni 调用包装器,该包装器反过来又调用平台的本机系统() 功能。但是,请记住 system() 是阻塞的,因此您可能需要从它自己的线程中执行此操作。不过,您可能会遇到各种与 exec() 相关的复杂情况,具体取决于 JVM 的设置方式。另一种选择是找出当前终端是什么,并在子进程中将其作为标准输出打开,可能使用中介。
  • @ChrisStratton 终端?检测到虚假假设。是的,JNI 将是一种解决方案,由于复杂性,我不愿意追求。作为一个习惯性的 Java 程序员,我觉得很奇怪这种具有非常丰富和复杂的库的语言似乎没有解决这个简单任务的方法。
  • 然后不要使用终端,但无论当前输出是什么 - 在 linux 上,如果没有其他内容,您可以从 /proc/self/fd 中的链接中找到它。更根本的是,有一个标准的 java 解决方案可以解决这个问题,但您拒绝了它,因为您错误地认为您想要做的事情的方式会有所不同。

标签: java exec file-descriptor


【解决方案1】:

这是 Java 7 中引入的ProcessBuilder.redirectError/redirectOutput 的意图。使用Redirect.INHERIT 将使子进程与 Java 进程共享 stderr/stdout:

class A {
    public static void main(String[] args) {
        try {
            ProcessBuilder builder = new ProcessBuilder("echo", "foo");
            builder.redirectError(ProcessBuilder.Redirect.INHERIT);
            builder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
            Process p = builder.start();
            p.waitFor();
        } catch (Exception e) {
            System.err.println(e);
            System.exit(1);
        }
    }
}

【讨论】:

    【解决方案2】:

    您可以查看NuProcess 项目。免责声明:我写的。它提供来自衍生进程的非阻塞 I/O。您仍然必须在 Java 中进行中继(您会收到回调),但是因为它在 Linux 的情况下使用 epoll(),所以我希望它能够保留底层程序的顺序。只有一个线程在对管道进行 epoll() 处理,因此您不会遇到任何线程调度顺序问题。

    我 100% 的订单将保留在 MacOS X 或任何 BSD 变体上,因为它使用绝对有序的 kqueue。无论如何,您可能想试一试,代码和测试都很简单。

    【讨论】:

      【解决方案3】:

      你不能。默认情况下,子进程的所有标准 I/O 都重定向到父进程(运行您的 java 程序的 jvm)。

      from the javadoc of the Process class:

      默认情况下,创建的子进程没有自己的终端或 安慰。 所有标准 I/O(即标准输入、标准输出、标准错误) 操作将被重定向到父进程,它们可以在 通过使用 getOutputStream() 方法获得的流访问, getInputStream() 和 getErrorStream()。父进程使用这些 流向子流程提供输入并从子流程中获取输出。因为 一些原生平台只为标准提供有限的缓冲区大小 输入输出流,未能及时写入输入流 或者读取子进程的输出流可能会导致子进程 阻塞,甚至死锁。

      【讨论】:

      • 我知道,默认情况下他们会重定向。我该如何改变呢?
      • 其实我的错。该 javadoc 的关注点是 the created subprocess does not have its own terminal or console。 “默认情况下”的使用在 javadoc 中是相当糟糕的单词选择。它只是意味着,这是唯一可能的方式(就在终端或控制台中运行而言)。
      • 我也不想创建终端——我不是在寻找expect。我希望子进程的输出可以去任何 Java 进程的输出去的地方。
      • @ChrisStratton 我请您参考我问题的第一段。这是一些问题的解决方案,但不是我的。我希望子进程的输出(stdout 和 stderr)直接连接到正在侦听 Java 程序的程序,而 Java 程序不是中间的中继。中继无法解决的一个常见问题是,当它们在同一通道上时,中继会将标准输出和标准错误混为一谈。不,我也不能合并它们,因为即使我不想合并它们,它们最终也会合并。
      • @ChrisStratton 我的问题已经包含等效要求。 (至少,它在 Linux 上是等价的,我相信其他的 unices,也许不是在 Windows 上。)我添加了这个作为一个激励性的例子。我确实在我的问题中明确提到不需要中继。
      猜你喜欢
      • 2012-09-12
      • 1970-01-01
      • 1970-01-01
      • 2015-11-20
      • 1970-01-01
      • 2016-04-10
      • 1970-01-01
      • 2013-10-13
      • 1970-01-01
      相关资源
      最近更新 更多