|
Running shell commands from Java is not always straightforward and intuitive, in this article I share some techniques I use to make executing shell commands with Java easier.
ProcessBuilder
Use Java's ProcessBuilder class instead of Runtime class for Java 5.0+ projects. There are two reasons:
- Easier output handling. ProcessBuilder has redirectErrorStream() method that automatically merges the standard output and standard error together. Not only is this convenient, it also prevents process from freezing if the error output stream is not cleared.
- Easier manipulation of environment. ProcessBuilder has environment() method that returns a Map the developer can use to manipulate the environment in which the shell command executes.
If you are lucky enough to be working with Java 5.0+, here is a code template to execute shell commands.
try
{
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.directory(new File(cwd));
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
BufferedReader br =
new BufferedReader(new InputStreamReader(process.getInputStream()));
String lineRead;
while ((lineRead = br.readLine()) != null)
{
System.out.println(lineRead);
}
int exitValue = process.waitFor();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
catch (InterruptedException ie)
{
ie.printStackTrace();
}
Runtime.exec()
Must use Java 1.4? The code is more complex then...
Merging Standard Output and Error
This is not optional, the developer much clear the standard error or the process might deadlock. I usually spawn two separate threads clear the standard output and standard error, both of which use the following sample code. Simple put this StreamGobbler.java file in your project, we will use this class later on.
import java.io.*;
public class StreamGobbler extends Thread
{
private InputStream inputStream;
StreamGobbler(InputStream inputStream)
{
this.inputStream = inputStream;
}
public void run()
{
try
{
BufferedReader br =
new BufferedReader(new InputStreamReader(inputStream));
String lineRead = null;
while ((lineRead = br.readLine()) != null)
{
System.out.println(lineRead);
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
Calling Shell Commands
Here is the code to call shell commands with Runtime.exec():
try
{
// see "Parsing Shell Commands" section
String[] commands = prepareCommand(command);
// the current work directory where the shell command executes
String cwd = "/somewhere";
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(commands, null, new File(cwd));
StreamGobbler outputGobbler =
new StreamGobbler(process.getInputStream());
StreamGobbler errorGobbler =
new StreamGobbler(process.getErrorStream());
outputGobbler.start();
errorGobbler.start();
int exitValue = process.waitFor();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
catch (InterruptedException ie)
{
ie.printStackTrace();
}
Parsing Shell Commands
Passing a shell command directly to Java does not always work. The way Java parses the commands does unexpected things, at least to me at first. I found that parsing the commands myself then passing them to Java guarantees correct execution. This command parsing algorithm divides one command string into an array of command parts; the command parts are divided by whitespace, unless the whitespace appears in quotation. Here is a simple method that prepares the command for Java.
private String[] prepareCommand(String command)
{
ArrayList commandParts = new ArrayList();
StringBuffer currPart = new StringBuffer();
boolean inQuote = false;
for (int x = 0; x < command.length(); x++)
{
char ch = command.charAt(x);
if (ch == ''' || ch == '"')
{
inQuote = !inQuote;
}
else if (Character.isWhitespace(ch) && !inQuote)
{
commandParts.add(currPart.toString());
currPart = new StringBuffer();
}
else
{
currPart.append(ch);
}
// get the last part of the command
if (x == command.length() - 1)
{
commandParts.add(currPart.toString());
}
}
String[] coms = new String[commandParts.size()];
commandParts.toArray(coms);
return coms;
}
Setting Executable Locations
Many times a developer will try to modify the PATH environment variable so Java can locate executables, that does NOT work. The environment settings do not take affect until the process has started, but Java cannot start the process because it cannot find the executable. One possible solution is to set the environment variables and then run the command "sh -c 'executable'".
|