Scripting languages like Bourne shell and perl have very simple constructs
to run external commands and capture their output.
For example, in Bourne shell you can run a command simply like this
jar cf foo.jar foo.class
and you can capture the output of a command simply like this:
output=`jar tf foo.jar`
Java has all the infrastructure needed for an application programmer to
do the same thing, but it is surprisingly difficult, especially if one
wants results to be reliable and never hang.
The reason for the difficulty is that the stdout and the stderr of a
Java Process is connected to the parent process via operating system
pipes. These have fixed size buffers, rather like ArrayBlockingQueue.
If the producer of data writes enough data to fill a buffer, it will
block until the consumer drains some of the data out of the buffer.
If the data is not drained by the consumer, then the producer will
block if and only if the data exceeds the size of the buffer.
As an example, in the simple shell command
cat file | sleep 10000000
whether the cat command will exit quickly depends on the size of the file *and*
the system-dependent size of the pipe buffer.
The only safe way to capture the output of a command from Java is
to start up two threads, one to read the stderr, and one to read the stdout,
and then to join these two threads. To force a user to confront
a difficult concurrent programming problem when the user is thinking that
the problem is serial is very bad. Even an enlightened programmer who
knows better might be tempted to write an unreliable expression like
new ProcessBuilder(cmd).start().waitFor();
to run a command that is "known" to never create any output, even when this
is not completely safe, to save the 50 lines of code required to do this
safely.
The core libraries should provide a way. One possible way is
ProcessResults res = new ProcessBuilder(cmd).run();
String stdout = res.stdout();
String stderr = res.stderr();
Here is some sample code from the regression test for ProcessBuilder:
private static class StreamAccumulator extends Thread {
private final InputStream is;
private final StringBuilder sb = new StringBuilder();
private Throwable throwable = null;
public String result () throws Throwable {
if (throwable != null)
throw throwable;
return sb.toString();
}
StreamAccumulator (InputStream is) {
this.is = is;
}
public void run() {
try {
Reader r = new InputStreamReader(is);
char[] buf = new char[4096];
int n;
while ((n = r.read(buf)) > 0) {
sb.append(buf,0,n);
}
} catch (Throwable t) {
throwable = t;
}
}
}
private static ProcessResults run(Process p) {
Throwable throwable = null;
int exitValue = -1;
String out = "";
String err = "";
StreamAccumulator outAccumulator =
new StreamAccumulator(p.getInputStream());
StreamAccumulator errAccumulator =
new StreamAccumulator(p.getErrorStream());
try {
outAccumulator.start();
errAccumulator.start();
exitValue = p.waitFor();
outAccumulator.join();
errAccumulator.join();
out = outAccumulator.result();
err = errAccumulator.result();
} catch (Throwable t) {
throwable = t;
}
return new ProcessResults(out, err, exitValue, throwable);
}
//----------------------------------------------------------------
// Results of a command
//----------------------------------------------------------------
private static class ProcessResults {
private final String out;
private final String err;
private final int exitValue;
private final Throwable throwable;
public ProcessResults(String out,
String err,
int exitValue,
Throwable throwable) {
this.out = out;
this.err = err;
this.exitValue = exitValue;
this.throwable = throwable;
}
public String out() { return out; }
public String err() { return err; }
public int exitValue() { return exitValue; }
public Throwable throwable() { return throwable; }
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("<STDOUT>\n" + out() + "</STDOUT>\n")
.append("<STDERR>\n" + err() + "</STDERR>\n")
.append("exitValue = " + exitValue + "\n");
if (throwable != null)
sb.append(throwable.getStackTrace());
return sb.toString();
}
}
###@###.### 2004-07-15
to run external commands and capture their output.
For example, in Bourne shell you can run a command simply like this
jar cf foo.jar foo.class
and you can capture the output of a command simply like this:
output=`jar tf foo.jar`
Java has all the infrastructure needed for an application programmer to
do the same thing, but it is surprisingly difficult, especially if one
wants results to be reliable and never hang.
The reason for the difficulty is that the stdout and the stderr of a
Java Process is connected to the parent process via operating system
pipes. These have fixed size buffers, rather like ArrayBlockingQueue.
If the producer of data writes enough data to fill a buffer, it will
block until the consumer drains some of the data out of the buffer.
If the data is not drained by the consumer, then the producer will
block if and only if the data exceeds the size of the buffer.
As an example, in the simple shell command
cat file | sleep 10000000
whether the cat command will exit quickly depends on the size of the file *and*
the system-dependent size of the pipe buffer.
The only safe way to capture the output of a command from Java is
to start up two threads, one to read the stderr, and one to read the stdout,
and then to join these two threads. To force a user to confront
a difficult concurrent programming problem when the user is thinking that
the problem is serial is very bad. Even an enlightened programmer who
knows better might be tempted to write an unreliable expression like
new ProcessBuilder(cmd).start().waitFor();
to run a command that is "known" to never create any output, even when this
is not completely safe, to save the 50 lines of code required to do this
safely.
The core libraries should provide a way. One possible way is
ProcessResults res = new ProcessBuilder(cmd).run();
String stdout = res.stdout();
String stderr = res.stderr();
Here is some sample code from the regression test for ProcessBuilder:
private static class StreamAccumulator extends Thread {
private final InputStream is;
private final StringBuilder sb = new StringBuilder();
private Throwable throwable = null;
public String result () throws Throwable {
if (throwable != null)
throw throwable;
return sb.toString();
}
StreamAccumulator (InputStream is) {
this.is = is;
}
public void run() {
try {
Reader r = new InputStreamReader(is);
char[] buf = new char[4096];
int n;
while ((n = r.read(buf)) > 0) {
sb.append(buf,0,n);
}
} catch (Throwable t) {
throwable = t;
}
}
}
private static ProcessResults run(Process p) {
Throwable throwable = null;
int exitValue = -1;
String out = "";
String err = "";
StreamAccumulator outAccumulator =
new StreamAccumulator(p.getInputStream());
StreamAccumulator errAccumulator =
new StreamAccumulator(p.getErrorStream());
try {
outAccumulator.start();
errAccumulator.start();
exitValue = p.waitFor();
outAccumulator.join();
errAccumulator.join();
out = outAccumulator.result();
err = errAccumulator.result();
} catch (Throwable t) {
throwable = t;
}
return new ProcessResults(out, err, exitValue, throwable);
}
//----------------------------------------------------------------
// Results of a command
//----------------------------------------------------------------
private static class ProcessResults {
private final String out;
private final String err;
private final int exitValue;
private final Throwable throwable;
public ProcessResults(String out,
String err,
int exitValue,
Throwable throwable) {
this.out = out;
this.err = err;
this.exitValue = exitValue;
this.throwable = throwable;
}
public String out() { return out; }
public String err() { return err; }
public int exitValue() { return exitValue; }
public Throwable throwable() { return throwable; }
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("<STDOUT>\n" + out() + "</STDOUT>\n")
.append("<STDERR>\n" + err() + "</STDERR>\n")
.append("exitValue = " + exitValue + "\n");
if (throwable != null)
sb.append(throwable.getStackTrace());
return sb.toString();
}
}
###@###.### 2004-07-15
- duplicates
-
JDK-4960438 (process) Need IO redirection API for subprocesses
- Closed
- relates to
-
JDK-5073268 (process) Process.waitFor does not return when executing "sh -c df <nfs dir>"
- Closed
-
JDK-4985017 (process) Process.waitFor waits for subprocess IO to complete
- Closed
-
JDK-6423785 (process) need Java equivalent of popen and system
- Open