如何将进程输出导向到Windows和JDK 6u45上的文件

9
我可以帮助您翻译以下的 Windows 批处理文件(run.bat):

我有以下的 Windows 批处理文件(run.bat):

@echo off
echo hello batch file to sysout

以下是运行批处理文件并将输出重定向到文件的Java代码:
public static void main(String[] args) throws IOException {
    System.out.println("Current java version is: " + System.getProperty("java.version"));

    ProcessBuilder pb =
            new ProcessBuilder("cmd.exe", "/c",
                    "run.bat"
                     ,">>", "stdout.txt","2>>", "stderr.txt"
                    );
    System.out.println("Command is: " + pb.command());

    Process proc = pb.start();

    InputStream in = proc.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(in));

    String line = null;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }

    int exitValue = proc.exitValue();
    System.out.println("Exit value: " + exitValue);
}

在包括JDK6u43及以前版本的JDK中,我得到了以下输出:

Current java version is: 1.6.0_29
Command is: [cmd.exe, /c, run.bat, >>, stdout.txt, 2>>, stderr.txt]
Exit value: 0

脚本输出写入文件。 自JDK 6u45和7起,我得到以下输出:

Current java version is: 1.6.0_45
Command is: [cmd.exe, /c, run.bat, >>, stdout.txt, 2>>, stderr.txt]
hello batch file to sysout
Exit value: 0

并没有将任何内容写入输出文件。

这可能与在 Runtime.exec() 中所做的更改有关,详见: http://www.oracle.com/technetwork/java/javase/6u45-relnotes-1932876.html

在 Windows 上启动进程并将输出重定向到文件的正确方法是什么?

注意:在实际应用中,要执行的命令可能包括具有空格的参数,例如:

ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c",
"run.bat", "Some Input With Spaces", 
">>", "stdout.txt","2>>", "stderr.txt");

\c 右侧的所有命令行参数组合成单个参数是否有帮助? - David R Tribble
只有在脚本传递的参数中没有空格时,这才有效。 - Barak
3个回答

19

1
redirectOutput方法仅在Java 7及以上版本中可用,但需要在Java 6上运行。 - Barak

4

这里有几个建议:

  • 输入中的空格需要被视为单个字符串(包含空格),还是多个不同的输入?如果是前者的话,我建议在Windows运行时中引用它。
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", 
"run.bat", "\"Some Input With Spaces\"", 
">>", "stdout.txt","2>>", "stderr.txt");
  • 不要使用shell将输入重定向到stdout.txt和stderr.txt,为什么不使用Java中的getOutputStream()和getErrorStream()进行重定向呢?以下是使用Guava的IO包的示例。当然,您可能希望在单独的线程中运行这些操作,需要正确处理异常等等。
InputStream stdout = new BufferedInputStream(proc.getInputStream());
FileOutputStream stdoutFile = new FileOutputStream("stdout.txt");
ByteStreams.copy(stdout, stdoutFile);

InputStream stderr = new BufferedInputStream(proc.getErrorStream());
FileOutputStream stderrFile = new FileOutputStream("stderr.txt");
ByteStreams.copy(stderr, stderrFile);

stdout.close();
stderr.close();
stdoutFile.close();
stderrFile.close();
  • 为什么不创建一个run.bat包装器来进行重定向呢?
@echo off
cmd.exe /c run.bat "%1" >> "%2" 2>> "%3"

执行的进程可能是长时间运行的。如果我在Java进程中读取进程输出并且它死亡,进程输出将被缓冲,最终外部进程将停止。Java进程不应以任何方式干扰外部进程。这就是为什么管道传输到文件应该由操作系统完成的原因。 - Barak
关于输入中的空格,任何选项都是可能的。命令行使用List<String>进行配置,允许传递任何组合。在6u45 JDK之前,ProcessBuilder实现可以毫无问题地工作。 - Barak
创建一个包装脚本是一种选择,但是该脚本需要与外部脚本位于同一目录中(以防脚本中引用了其他文件和目录)。这是“不干净”的 - 我希望以尽可能不显眼的方式运行代码,并且添加生成的文件可能会有问题。 - Barak
使用包装脚本似乎是在JDK 6中以可移植的方式完成此操作的唯一方法,因此将此答案标记为正确。 - Barak
@Barak 你写道,手头的进程运行时间很长。将其注册为Windows服务是否有意义? - David Rabinowitz
不一定。系统的最终用户设置脚本,因此我们事先不知道它的功能。如果用户希望,可以设置任务计划程序或Windows服务。 - Barak

0

在处理过程中使用getOutputStream(),而不是使用System.out.println()。有时Java实现之间的语义会发生变化。

实际上,这似乎是一个错误修复 - 新的实现更合理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接