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);
Leave a Reply