Monday, August 20, 2012

Java's Runtime.exec() and External Applications

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.

1 comment:

  1. It shows you a trick to deal with redirection of commands output in shell

    ReplyDelete