9

I have the following windows batch file (run.bat):

@echo off
echo hello batch file to sysout

And the following java code, which runs the batch files and redirects output to a file:

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

On JDKs up to and including JDK6u43 I get the following output:

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

and the script output is written to the file. As of JDK 6u45 and 7, I get the following output:

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

And nothing is written to the output file.

This may or may not be related to the changes made in Runtime.exec() , described at: http://www.oracle.com/technetwork/java/javase/6u45-relnotes-1932876.html

What is the correct way of starting a process on Windows with output redirected to files?

Note: In a real world scenario, the command to execute may include parameters with spaces, as in:

ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c",
"run.bat", "Some Input With Spaces", 
">>", "stdout.txt","2>>", "stderr.txt");
itaifrenkel
  • 1,578
  • 1
  • 12
  • 26
Barak
  • 3,066
  • 2
  • 20
  • 33
  • Does it help to combine all of the command line args to the right of `\c` into a single arg? – David R Tribble Jun 13 '13 at 18:21
  • This only works if I have no spaces in the arguments passed to the script. – Barak Jun 15 '13 at 08:27
  • See also [ProcessBuilder: Forwarding stdout and stderr of started processes without blocking the main thread](https://stackoverflow.com/questions/14165517/processbuilder-forwarding-stdout-and-stderr-of-started-processes-without-blocki) – Vadzim Feb 07 '20 at 15:11

3 Answers3

19

This is the simplest method i found on http://tamanmohamed.blogspot.in/2012/06/jdk7-processbuilder-and-how-redirecting.html

File output = new File("C:/PBExample/ProcessLog.txt");
ProcessBuilder pb = new ProcessBuilder("cmd");
pb.redirectOutput(output);
Sanjay Bhosale
  • 685
  • 2
  • 8
  • 18
  • 1
    The redirectOutput method is only available from Java 7 and this needs to work on Java 6 too. – Barak Sep 06 '13 at 16:58
4

Several suggestions here:

  • Does the input with the spaces need to be treated as single String (with spaces),or id it in actual several inputs? If the first Option is the case I would suggest to quote it for the windows runtime:

ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", 
"run.bat", "\"Some Input With Spaces\"", 
">>", "stdout.txt","2>>", "stderr.txt");
  • Instead of redirecting the input to stdout.txt and stderr.txt using the shell, why not do it using Java using getOutputStream() and getErrorStream()? Here is an example using Guava's IO package. Of course you may want to have those in separate threads, you need proper exception handling, etc.

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();
  • Another option, why not create a run.bat wrapper that will make the redirections?

@echo off
cmd.exe /c run.bat "%1" >> "%2" 2>> "%3"
Jorge Campos
  • 22,647
  • 7
  • 56
  • 87
David Rabinowitz
  • 29,904
  • 14
  • 93
  • 125
  • The process that is executed may be long running. If I read the process output in the Java process and it dies, the process output will be buffered and eventually the external process will halt. The Java process should not interfere with the external one in any way. That is why piping to the file should be done by the OS, somehow. – Barak Jun 15 '13 at 08:25
  • Regarding spaces in the input, any options is possible. The command line is configured using a List, allowing any combination to be passed. With the pre 6u45 JDK, the ProcessBuilder implementation worked like this without any problem. – Barak Jun 15 '13 at 08:26
  • Creating a wrapper script is an option, but the script would need to be in the same directory as the external script (in case there are additional files and directories references in the script). This is 'dirty' - I want to run the code in as unobtrusive way as possible, and adding generated files can be problematic. – Barak Jun 16 '13 at 09:14
  • Using a wrapper script seems to be the only way to do this in a portable fashion for JDK 6, so marking this answer as correct. – Barak Jun 18 '13 at 17:43
  • @Barak You wrote that the process at hand is long running. Would it make sense to register it as a windows service? – David Rabinowitz Jun 18 '13 at 18:56
  • Not necessarily. The end-user of the system sets up the script, so we don't know in advance what it does. The user set up task scheduler or a windows service if he wants – Barak Jun 19 '13 at 07:22
0

Use getOutputStream() on the process, instead of using System.out.println(). Sometimes the semantics change between Java implementations.

This seems to be a bugfix actually - the newer implementation makes sense.

blackpanther
  • 10,998
  • 11
  • 48
  • 78
TFuto
  • 1,361
  • 15
  • 33