Java was developed with an eye toward platform independence, freeing the
software developer from the unpleasant and tedious task of cross-platform
porting and testing of software. All too often, however, developers are not
able to write completely new applications, but have to interface to existing
legacy software and operating systems. This poses a problem as Java cannot
make use of many utilities and system-dependent features that these programs
have relied upon in the past.
There are two solutions to this problem, both of which sacrifice platform
independence. The first is to make use of native methods in your Java code.
You lose the portability, but achieve the result. Native methods can also be
more difficult to implement, which can make them less desirable to use.
The second option is to satisfy the programming needs by executing external
utilities and applications from within a Java program. In Java, this means
employing the Runtime class exec() method. Like the fork(3) C function,
Runtime.exec() allows you to execute a program; unlike fork(), it does not
allow you to directly control the environment. Runtime.exec() provides a
simple interface to platform-dependent utilities and applications, although
at the expense of platform independence. In an environment where Java
applications must coexist with other non-Java applications this will often be
a valid trade-off.
In its simplest form, exec() is quite easy to use:
Process p = Runtime.getRuntime().exec("/bin/ls");
The problem with this form is that it gets you nowhere. When Java forks a
new process it redirects STDIN, STDOUT and STDERR. Therefore the results of
"/bin/ls"
do not go to the screen. Instead you must use the
getInputStream(), getOutputStream() and getErrorStream() methods of the
Process object to communicate with the program that you executed. Listing 1
shows an example using the Unix ls(1) command.
Listing 1
import java.io.*;
class execInput {
public static void main(String Argv[]) {
try {
String ls_str;
Process ls_proc = Runtime.getRuntime().exec("/bin/ls -aFl");
// get its output (your input) stream
DataInputStream ls_in = new DataInputStream(
ls_proc.getInputStream());
try {
while ((ls_str = ls_in.readLine()) != null) {
System.out.println(ls_str);
}
} catch (IOException e) {
System.exit(0);
}
} catch (IOException e1) {
System.err.println(e1);
System.exit(1);
}
System.exit(0);
}
}
Some points to be aware of with this code. In the JDK1.0.2 implementation,
you must supply a complete path to whatever it is that you are executing.
There is no facility in that version of the JDK to get the shell environment
that you are running in, hence no PATH variable is known. This is fixed in
JDK1.1, however it is always good practice to fully qualify the path.
Shell built-in commands will not work here. A prime example of this is the
DOS "dir" command. This is a built-in and will not execute. You would need
to use "
command \c dir
" as the command string.
If you pass the executable command as a single String, the exec() method will
break the string into multiple strings, breaking the original string on white
space. This will cause some trouble when trying to do things like I/O
redirection.
Consider the following line of code:
Process p = Runtime.getRuntime().exec("/bin/sh -c /bin/ls > ls.out");
This is intended to execute a Bourne shell and have the shell execute the ls
command, redirecting the output of ls to the file ls.out. The reason for
using /bin/sh is to get around the problem of having stdout redirected by the
Java internals. Unfortunately, if you try this nothing will happen. When
this command string is passed to the exec() method it will be broken into an
array of Strings with the elements being
"/bin/sh", "-c", "/bin/ls",
">", and "ls.out"
. This will fail, as sh expects only a single
argument to the "-c" switch. To make this work try:
String[] cmd = {"/bin/sh", "-c", "/bin/ls > out.dat"};
Process p = Runtime.getRuntime().exec(cmd);
Since the command line is already a series of Strings, the strings will
simply be loaded into the command array by the exec() method and passed to
the new process as is. Thus the shell will see a "-c" and the command
"
/bin/ls > ls.out
" and execute correctly.
Using Runtime.exec() can make integration with system utilities, tools and
legacy software achievable, although, as already noted, at the expense
platform independence. If you must work with existing software and tools, it
is often the easiest, and most straight-forward approach to the problem. But
beware of the gotchas. They can often lead to unexpected results.