I recently redesigned some of the code I tend to use to spawn external processes (pdflatex anyone?) in java. The implementation is still a bit buggy, but I am more interested in people’s opinions about the API (non-blocking killable invocations are not yet supported). The project on github is called jproc. Here is the cookbook so far:
Get the library from maven central:
<dependency>
<groupId>org.buildobjects</groupId>
<artifactId>jproc</artifactId>
<version>2.0.1</version>
</dependency>
To launch an external program we’ll use a ProcBuilder
. The run method
builds and spawns the actual process and blocks until the process exits.
The process takes care of writing the output to a stream (as opposed to the standard
facilities in the JDK that expect the client to actively consume the
output from an input stream:
ByteArrayOutputStream output = new ByteArrayOutputStream();
new ProcBuilder("echo")
.withArg("Hello World!")
.withOutputStream(output)
.run();
assertEquals("Hello World!\n", output.toString());
The input can be read from an arbitrary input stream, like this:
ByteArrayInputStream input = new ByteArrayInputStream("Hello cruel World".getBytes());
ProcResult result = new ProcBuilder("wc")
.withArgs("-w")
.withInputStream(input).run();
assertEquals("3", result.getOutputString().trim());
If all you want to get is the string that gets returned and if there
is not a lot of data, using a streams is quite cumbersome. So for convenience
if no stream is provdied the output is captured by default and can be
obtained from the result.
ProcResult result = new ProcBuilder("echo")
.withArg("Hello World!")
.run();
assertEquals("Hello World!\n", result.getOutputString());
assertEquals(0, result.getExitValue());
assertEquals("echo \"Hello World!\"", result.getProcString());
For providing input there is a convenience method too:
ProcResult result = new ProcBuilder("cat")
.withInput("This is a string").run();
assertEquals("This is a string", result.getOutputString());
Some external programs are using environment variables. These can also
be set using the withVar
method
ProcResult result = new ProcBuilder("bash")
.withArgs("-c", "echo $MYVAR")
.withVar("MYVAR","my value").run();
assertEquals("my value\n", result.getOutputString());
assertEquals("bash -c \"echo $MYVAR\"", result.getProcString());
A common usecase for external programs is batch processing of data.
These programs might always run into difficulties. Therefore a timeout can be
specified. There is a default timeout of 5000ms. If the program does not terminate
within the timeout interval it will be terminated and the failure is indicated through
an exception:
ProcBuilder builder = new ProcBuilder("sleep")
.withArg("2")
.withTimeoutMillis(1000);
try {
builder.run();
fail("Should time out");
}
catch (TimeoutException ex){
assertEquals("Process 'sleep 2' timed out after 1000ms.", ex.getMessage());
}
Even if the process does not timeout, we might be interested in the
execution time. It is also available through the result:
ProcResult result = new ProcBuilder("sleep")
.withArg("0.5")
.withTimeoutMillis(1000)
.run();
assertTrue(result.getExecutionTime() > 500 && result.getExecutionTime() < 1000);
By default the new program is spawned in the working directory of
the parent process. This can be overidden:
ProcResult result = new ProcBuilder("pwd")
.withWorkingDirectory(new File("/"))
.run();
assertEquals("/\n", result.getOutputString());
It is a time honoured tradition that programs signal a failure
by returning a non-zero exit value. However in java failure is
signalled through exceptions. Non-Zero exit values therefore
get translated into an exception, that also grants access to
the output on standard error.
ProcBuilder builder = new ProcBuilder("ls")
.withArg("xyz");
try {
builder.run();
fail("Should throw exception");
} catch (ExternalProcessFailureException ex){
assertEquals("ls: xyz: No such file or directory\n", ex.getStderr());
assertEquals(1, ex.getExitValue());
assertEquals("ls xyz", ex.getCommand());
assertEquals("ls: xyz: No such file or directory\n", ex.getStderr());
assertTrue(ex.getTime() > 0);
}
Input and output can also be provided as byte[]
.
ProcBuilder
also copes with large amounts of
data.
int MEGA = 1024 * 1024;
byte[] data = new byte[4 * MEGA];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) Math.round(Math.random() * 255 - 128);
}
ProcResult result = new ProcBuilder("gzip")
.withInput(data)
.run();
assertTrue(result.getOutputBytes().length > 2 * MEGA);
The builder allows to build and spawn several processes from
the same builder instance:
ProcBuilder builder = new ProcBuilder("uuidgen");
String uuid1 = builder.run().getOutputString();
String uuid2 = builder.run().getOutputString();
assertNotNull(uuid1);
assertNotNull(uuid2);
assertTrue(!uuid1.equals(uuid2));
For convenience there is also a static method that just runs a
program and captures the ouput:
String output = ProcBuilder.run("echo", "Hello World!");
assertEquals("Hello World!\n", output);
Also there is a static method that filters a given string through
a program:
String output = ProcBuilder.filter("x y z","sed" ,"s/y/a/");
assertEquals("x a z\n", output);