The generated script approach to running shell commands from Java

Sunday, June 29, 2008

Running external process from Java is simple enough using Runtime.exec() but there are some well documented limitations which are covered in detail in an old but still relevant JavaWorld article entitled When Runtime.exec() won't.

I recently needed to launch a MySQL backup from Java but needed to redirect stdout and stderr to file. Unfortunately, Runtime.exec() can only be used to run an executable file and pass parameters in. It does not support more complex operations such as piping the output of one process into another process or even redirecting stdout or stderr. In other words, Runtime.exec() can’t be used as a linux command line replacement.

For example, this works correctly:
Runtime.getRuntime().exec( “/usr/bin/mysqldump mydb --result-file=mydb.dump” );


But this will not work:

Runtime.getRuntime().exec( “/usr/bin/mysqldump mydb --result-file=mydb.dump >stdout.txt 2>stderr.txt” );


This last code fragment runs without error but the stdout.txt and stderr.txt files are not created.

The standard solution to this problem is to write Java code to launch two threads, one to read the output stream from the process and one to read the error stream and then write the output from those streams to disk from within Java, but this seems like a heavyweight solution in this instance. There is also a risk of the subprocess hanging if the Java code does not read the output from the process quickly enough, as outlined in the javadocs:

“Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.”

A simpler solution to allow arbitrary linux command lines to be run from Java is to write the command to a shell script and then execute the shell script. For example:


private static void runCommand(String cmd) throws IOException, InterruptedException {

// generate a script file containg the command to run
final File scriptFile = new File("/tmp/runcommand.sh");
PrintWriter w =
new PrintWriter(scriptFile);
w.println(
"#!/bin/sh" );
w.println( cmd );
w.close();

// make the script executable
Process p = Runtime.getRuntime().exec(
"chmod +x " + scriptFile.getAbsolutePath() );
p.waitFor();

// execute the script
p = Runtime.getRuntime().exec( scriptFile.getAbsolutePath() );
p.waitFor();
}


Using this approach, I can now simply run:

runCommand(“/usr/bin/mysqldump mydb --result-file=mydb.dump >stdout.txt 2>stderr.txt”);


This approach works fine for my requirement, without the overhead of creating additional Java threads. However, this approach is not suitable if the Java application needs to read the output of the process before the process has completed, in which case the standard approach of launching threads to read the output streams should be used.

The generated script approach is very convenient for enabling general purpose linux command line usage from Java.

Labels:

1 Comments:

Blogger prabhu said...

Hi Grove,

I saw your post, which was very much helpful to me. However to restore my database i tried a command by this way "mysql -u username -ppass databasename < c:/backup.sql". But this is not working. This requirement should work both in linux machine and windows machine.

Please help me to solve this issue.

My email id: prabhu.rangan@sysvine.com

November 18, 2008 2:50 AM  

Post a Comment

<< Home