`
lee79
  • 浏览: 108904 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

When Runtime.exec() won't

阅读更多

As part of the Java language, the java.lang package is implicitly imported into every Java program. This package's pitfalls surface often, affecting most programmers. This month, I'll discuss the traps lurking in the Runtime.exec() method.

Pitfall 4: When Runtime.exec() won't


The class java.lang.Runtime features a static method called getRuntime(), which retrieves the current Java Runtime Environment. That is the only way to obtain a reference to the Runtime object. With that reference, you can run external programs by invoking the Runtime class's exec() method. Developers often call this method to launch a browser for displaying a help page in HTML.

There are four overloaded versions of the exec() command:

    * public Process exec(String command);
    * public Process exec(String [] cmdArray);
    * public Process exec(String command, String [] envp);
    * public Process exec(String [] cmdArray, String [] envp);

For each of these methods, a command -- and possibly a set of arguments -- is passed to an operating-system-specific function call. This subsequently creates an operating-system-specific process (a running program) with a reference to a Process class returned to the Java VM. The Process class is an abstract class, because a specific subclass of Process exists for each operating system.

You can pass three possible input parameters into these methods:

   1. A single string that represents both the program to execute and any arguments to that program
   2. An array of strings that separate the program from its arguments
   3. An array of environment variables

Pass in the environment variables in the form name=value. If you use the version of exec() with a single string for both the program and its arguments, note that the string is parsed using white space as the delimiter via the StringTokenizer class.
Stumbling into an IllegalThreadStateException

The first pitfall relating to Runtime.exec() is the IllegalThreadStateException. The prevalent first test of an API is to code its most obvious methods. For example, to execute a process that is external to the Java VM, we use the exec() method. To see the value that the external process returns, we use the exitValue() method on the Process class. In our first example, we will attempt to execute the Java compiler (javac.exe):

 

Listing 4.1 BadExecJavac.java

 

import java.util.*;
import java.io.*;
public class BadExecJavac
{
    public static void main(String args[])
    {
        try
        {            
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec("javac");
            int exitVal = proc.exitValue();
            System.out.println("Process exitValue: " + exitVal);
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}

 

A run of BadExecJavac produces:

 

E:\classes\com\javaworld\jpitfalls\article2>java BadExecJavac
java.lang.IllegalThreadStateException: process has not exited
        at java.lang.Win32Process.exitValue(Native Method)
        at BadExecJavac.main(BadExecJavac.java:13)

 

If an external process has not yet completed, the exitValue() method will throw an IllegalThreadStateException; that's why this program failed. While the documentation states this fact, why can't this method wait until it can give a valid answer?

 

A more thorough look at the methods available in the Process class reveals a waitFor() method that does precisely that. In fact, waitFor() also returns the exit value, which means that you would not use exitValue() and waitFor() in conjunction with each other, but rather would choose one or the other. The only possible time you would use exitValue() instead of waitFor() would be when you don't want your program to block waiting on an external process that may never complete. Instead of using the waitFor() method, I would prefer passing a boolean parameter called waitFor into the exitValue() method to determine whether or not the current thread should wait. A boolean would be more beneficial because exitValue() is a more appropriate name for this method, and it isn't necessary for two methods to perform the same function under different conditions. Such simple condition discrimination is the domain of an input parameter.

Therefore, to avoid this trap, either catch the IllegalThreadStateException or wait for the process to complete.

Now, let's fix the problem in Listing 4.1 and wait for the process to complete. In Listing 4.2, the program again attempts to execute javac.exe and then waits for the external process to complete:

 

Listing 4.2 BadExecJavac2.java

 

import java.util.*;
import java.io.*;
public class BadExecJavac2
{
    public static void main(String args[])
    {
        try
        {            
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec("javac");
            int exitVal = proc.waitFor();
            System.out.println("Process exitValue: " + exitVal);
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}

 

Unfortunately, a run of BadExecJavac2 produces no output. The program hangs and never completes. Why does the javac process never complete?
Why Runtime.exec() hangs

The JDK's Javadoc documentation provides the answer to this question:

    Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

Is this just a case of programmers not reading the documentation, as implied in the oft-quoted advice: read the fine manual (RTFM)? The answer is partially yes. In this case, reading the Javadoc would get you halfway there; it explains that you need to handle the streams to your external process, but it does not tell you how.

Another variable is at play here, as is evident by the large number of programmer questions and misconceptions concerning this API in the newsgroups: though Runtime.exec() and the Process APIs seem extremely simple, that simplicity is deceiving because the simple, or obvious, use of the API is prone to error. The lesson here for the API designer is to reserve simple APIs for simple operations. Operations prone to complexities and platform-specific dependencies should reflect the domain accurately. It is possible for an abstraction to be carried too far. The JConfig library provides an example of a more complete API to handle file and process operations (see Resources below for more information).

 

Now, let's follow the JDK documentation and handle the output of the javac process. When you run javac without any arguments, it produces a set of usage statements that describe how to run the program and the meaning of all the available program options. Knowing that this is going to the stderr stream, you can easily write a program to exhaust that stream before waiting for the process to exit. Listing 4.3 completes that task. While this approach will work, it is not a good general solution. Thus, Listing 4.3's program is named MediocreExecJavac; it provides only a mediocre solution. A better solution would empty both the standard error stream and the standard output stream. And the best solution would empty these streams simultaneously (I'll demonstrate that later).

Listing 4.3 MediocreExecJavac.java

 

import java.util.*;
import java.io.*;
public class MediocreExecJavac
{
    public static void main(String args[])
    {
        try
        {            
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec("javac");
            InputStream stderr = proc.getErrorStream();
            InputStreamReader isr = new InputStreamReader(stderr);
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            System.out.println("<ERROR>");
            while ( (line = br.readLine()) != null)
                System.out.println(line);
            System.out.println("</ERROR>");
            int exitVal = proc.waitFor();
            System.out.println("Process exitValue: " + exitVal);
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}

 

A run of MediocreExecJavac generates:

 

E:\classes\com\javaworld\jpitfalls\article2>java MediocreExecJavac
<ERROR>
Usage: javac <options> <source files>
where <options> includes:
  -g                     Generate all debugging info
  -g:none                Generate no debugging info
  -g:{lines,vars,source} Generate only some debugging info
  -O                     Optimize; may hinder debugging or enlarge class files
  -nowarn                Generate no warnings
  -verbose               Output messages about what the compiler is doing
  -deprecation           Output source locations where deprecated APIs are used
  -classpath <path>      Specify where to find user class files
  -sourcepath <path>     Specify where to find input source files
  -bootclasspath <path>  Override location of bootstrap class files
  -extdirs <dirs>        Override location of installed extensions
  -d <directory>         Specify where to place generated class files
  -encoding <encoding>   Specify character encoding used by source files
  -target <release>      Generate class files for specific VM version
</ERROR>

 

Process exitValue: 2

So, MediocreExecJavac works and produces an exit value of 2. Normally, an exit value of 0 indicates success; any nonzero value indicates an error. The meaning of these exit values depends on the particular operating system. A Win32 error with a value of 2 is a "file not found" error. That makes sense, since javac expects us to follow the program with the source code file to compile.

Thus, to circumvent the second pitfall -- hanging forever in Runtime.exec() -- if the program you launch produces output or expects input, ensure that you process the input and output streams.
Assuming a command is an executable program

Under the Windows operating system, many new programmers stumble upon Runtime.exec() when trying to use it for nonexecutable commands like dir and copy. Subsequently, they run into Runtime.exec()'s third pitfall. Listing 4.4 demonstrates exactly that:

 

Listing 4.4 BadExecWinDir.java

 

import java.util.*;
import java.io.*;
public class BadExecWinDir
{
    public static void main(String args[])
    {
        try
        {            
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec("dir");
            InputStream stdin = proc.getInputStream();
            InputStreamReader isr = new InputStreamReader(stdin);
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            System.out.println("<OUTPUT>");
            while ( (line = br.readLine()) != null)
                System.out.println(line);
            System.out.println("</OUTPUT>");
            int exitVal = proc.waitFor();            
            System.out.println("Process exitValue: " + exitVal);
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}
 

A run of BadExecWinDir produces:

E:\classes\com\javaworld\jpitfalls\article2>java BadExecWinDir
java.io.IOException: CreateProcess: dir error=2
        at java.lang.Win32Process.create(Native Method)
        at java.lang.Win32Process.<init>(Unknown Source)
        at java.lang.Runtime.execInternal(Native Method)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at BadExecWinDir.main(BadExecWinDir.java:12)

As stated earlier, the error value of 2 means "file not found," which, in this case, means that the executable named dir.exe could not be found. That's because the directory command is part of the Windows command interpreter and not a separate executable. To run the Windows command interpreter, execute either command.com or cmd.exe, depending on the Windows operating system you use. Listing 4.5 runs a copy of the Windows command interpreter and then executes the user-supplied command (e.g., dir).

Listing 4.5 GoodWindowsExec.java

 

import java.util.*;
import java.io.*;
class StreamGobbler extends Thread
{
    InputStream is;
    String type;
    
    StreamGobbler(InputStream is, String type)
    {
        this.is = is;
        this.type = type;
    }
    
    public void run()
    {
        try
        {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String line=null;
            while ( (line = br.readLine()) != null)
                System.out.println(type + ">" + line);    
            } catch (IOException ioe)
              {
                ioe.printStackTrace();  
              }
    }
}
public class GoodWindowsExec
{
    public static void main(String args[])
    {
        if (args.length < 1)
        {
            System.out.println("USAGE: java GoodWindowsExec <cmd>");
            System.exit(1);
        }
        
        try
        {            
            String osName = System.getProperty("os.name" );
            String[] cmd = new String[3];
            if( osName.equals( "Windows NT" ) )
            {
                cmd[0] = "cmd.exe" ;
                cmd[1] = "/C" ;
                cmd[2] = args[0];
            }
            else if( osName.equals( "Windows 95" ) )
            {
                cmd[0] = "command.com" ;
                cmd[1] = "/C" ;
                cmd[2] = args[0];
            }
            
            Runtime rt = Runtime.getRuntime();
            System.out.println("Execing " + cmd[0] + " " + cmd[1] 
                               + " " + cmd[2]);
            Process proc = rt.exec(cmd);
            // any error message?
            StreamGobbler errorGobbler = new 
                StreamGobbler(proc.getErrorStream(), "ERROR");            
            
            // any output?
            StreamGobbler outputGobbler = new 
                StreamGobbler(proc.getInputStream(), "OUTPUT");
                
            // kick them off
            errorGobbler.start();
            outputGobbler.start();
                                    
            // any error???
            int exitVal = proc.waitFor();
            System.out.println("ExitValue: " + exitVal);        
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}

 

Running GoodWindowsExec with the dir command generates:

E:\classes\com\javaworld\jpitfalls\article2>java GoodWindowsExec "dir *.java"
Execing cmd.exe /C dir *.java
OUTPUT> Volume in drive E has no label.
OUTPUT> Volume Serial Number is 5C5F-0CC9
OUTPUT>
OUTPUT> Directory of E:\classes\com\javaworld\jpitfalls\article2
OUTPUT>
OUTPUT>10/23/00  09:01p                   805 BadExecBrowser.java
OUTPUT>10/22/00  09:35a                   770 BadExecBrowser1.java
OUTPUT>10/24/00  08:45p                   488 BadExecJavac.java
OUTPUT>10/24/00  08:46p                   519 BadExecJavac2.java
OUTPUT>10/24/00  09:13p                   930 BadExecWinDir.java
OUTPUT>10/22/00  09:21a                 2,282 BadURLPost.java
OUTPUT>10/22/00  09:20a                 2,273 BadURLPost1.java
... (some output omitted for brevity)
OUTPUT>10/12/00  09:29p                   151 SuperFrame.java
OUTPUT>10/24/00  09:23p                 1,814 TestExec.java
OUTPUT>10/09/00  05:47p                23,543 TestStringReplace.java
OUTPUT>10/12/00  08:55p                   228 TopLevel.java
OUTPUT>              22 File(s)         46,661 bytes
OUTPUT>                         19,678,420,992 bytes free
ExitValue: 0

Running GoodWindowsExec with any associated document type will launch the application associated with that document type. For example, to launch Microsoft Word to display a Word document (i.e., one with a .doc extension), type:

>java GoodWindowsExec "yourdoc.doc"

Notice that GoodWindowsExec uses the os.name system property to determine which Windows operating system you are running -- and thus determine the appropriate command interpreter. After executing the command interpreter, handle the standard error and standard input streams with the StreamGobbler class. StreamGobbler empties any stream passed into it in a separate thread. The class uses a simple String type to denote the stream it empties when it prints the line just read to the console.

Thus, to avoid the third pitfall related to Runtime.exec(), do not assume that a command is an executable program; know whether you are executing a standalone executable or an interpreted command. At the end of this section, I will demonstrate a simple command-line tool that will help you with that analysis.

It is important to note that the method used to obtain a process's output stream is called getInputStream(). The thing to remember is that the API sees things from the perspective of the Java program and not the external process. Therefore, the external program's output is the Java program's input. And that logic carries over to the external program's input stream, which is an output stream to the Java program.
Runtime.exec() is not a command line

One final pitfall to cover with Runtime.exec() is mistakenly assuming that exec() accepts any String that your command line (or shell) accepts. Runtime.exec() is much more limited and not cross-platform. This pitfall is caused by users attempting to use the exec() method to accept a single String as a command line would. The confusion may be due to the fact that command is the parameter name for the exec() method. Thus, the programmer incorrectly associates the parameter command with anything that he or she can type on a command line, instead of associating it with a single program and its arguments. In listing 4.6 below, a user tries to execute a command and redirect its output in one call to exec():

Listing 4.6 BadWinRedirect.java

 

import java.util.*;
import java.io.*;
// StreamGobbler omitted for brevity
public class BadWinRedirect
{
    public static void main(String args[])
    {
        try
        {            
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec("java jecho 'Hello World' > test.txt");
            // any error message?
            StreamGobbler errorGobbler = new 
                StreamGobbler(proc.getErrorStream(), "ERROR");            
            
            // any output?
            StreamGobbler outputGobbler = new 
                StreamGobbler(proc.getInputStream(), "OUTPUT");
                
            // kick them off
            errorGobbler.start();
            outputGobbler.start();
                                    
            // any error???
            int exitVal = proc.waitFor();
            System.out.println("ExitValue: " + exitVal);        
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}

 

Running BadWinRedirect produces:

E:\classes\com\javaworld\jpitfalls\article2>java BadWinRedirect
OUTPUT>'Hello World' > test.txt
ExitValue: 0

The program BadWinRedirect attempted to redirect the output of an echo program's simple Java version into the file test.txt. However, we find that the file test.txt does not exist. The jecho program simply takes its command-line arguments and writes them to the standard output stream. (You will find the source for jecho in the source code available for download in Resources.) In Listing 4.6, the user assumed that you could redirect standard output into a file just as you could on a DOS command line. Nevertheless, you do not redirect the output through this approach. The incorrect assumption here is that the exec() method acts like a shell interpreter; it does not. Instead, exec() executes a single executable (a program or script). If you want to process the stream to either redirect it or pipe it into another program, you must do so programmatically, using the java.io package. Listing 4.7 properly redirects the standard output stream of the jecho process into a file.

Listing 4.7 GoodWinRedirect.java

 

import java.util.*;
import java.io.*;
class StreamGobbler extends Thread
{
    InputStream is;
    String type;
    
    StreamGobbler(InputStream is, String type)
    {
        this.is = is;
        this.type = type;
    }
    
    public void run()
    {
        try
        {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String line=null;
            while ( (line = br.readLine()) != null)
                System.out.println(type + ">" + line);    
            } catch (IOException ioe)
              {
                ioe.printStackTrace();  
              }
    }
}
public class GoodWindowsExec
{
    public static void main(String args[])
    {
        if (args.length < 1)
        {
            System.out.println("USAGE: java GoodWindowsExec <cmd>");
            System.exit(1);
        }
        
        try
        {            
            String osName = System.getProperty("os.name" );
            String[] cmd = new String[3];
            if( osName.equals( "Windows NT" ) )
            {
                cmd[0] = "cmd.exe" ;
                cmd[1] = "/C" ;
                cmd[2] = args[0];
            }
            else if( osName.equals( "Windows 95" ) )
            {
                cmd[0] = "command.com" ;
                cmd[1] = "/C" ;
                cmd[2] = args[0];
            }
            
            Runtime rt = Runtime.getRuntime();
            System.out.println("Execing " + cmd[0] + " " + cmd[1] 
                               + " " + cmd[2]);
            Process proc = rt.exec(cmd);
            // any error message?
            StreamGobbler errorGobbler = new 
                StreamGobbler(proc.getErrorStream(), "ERROR");            
            
            // any output?
            StreamGobbler outputGobbler = new 
                StreamGobbler(proc.getInputStream(), "OUTPUT");
                
            // kick them off
            errorGobbler.start();
            outputGobbler.start();
                                    
            // any error???
            int exitVal = proc.waitFor();
            System.out.println("ExitValue: " + exitVal);        
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}

 

Running GoodWindowsExec with the dir command generates:

E:\classes\com\javaworld\jpitfalls\article2>java GoodWindowsExec "dir *.java"
Execing cmd.exe /C dir *.java
OUTPUT> Volume in drive E has no label.
OUTPUT> Volume Serial Number is 5C5F-0CC9
OUTPUT>
OUTPUT> Directory of E:\classes\com\javaworld\jpitfalls\article2
OUTPUT>
OUTPUT>10/23/00  09:01p                   805 BadExecBrowser.java
OUTPUT>10/22/00  09:35a                   770 BadExecBrowser1.java
OUTPUT>10/24/00  08:45p                   488 BadExecJavac.java
OUTPUT>10/24/00  08:46p                   519 BadExecJavac2.java
OUTPUT>10/24/00  09:13p                   930 BadExecWinDir.java
OUTPUT>10/22/00  09:21a                 2,282 BadURLPost.java
OUTPUT>10/22/00  09:20a                 2,273 BadURLPost1.java
... (some output omitted for brevity)
OUTPUT>10/12/00  09:29p                   151 SuperFrame.java
OUTPUT>10/24/00  09:23p                 1,814 TestExec.java
OUTPUT>10/09/00  05:47p                23,543 TestStringReplace.java
OUTPUT>10/12/00  08:55p                   228 TopLevel.java
OUTPUT>              22 File(s)         46,661 bytes
OUTPUT>                         19,678,420,992 bytes free
ExitValue: 0

Running GoodWindowsExec with any associated document type will launch the application associated with that document type. For example, to launch Microsoft Word to display a Word document (i.e., one with a .doc extension), type:

>java GoodWindowsExec "yourdoc.doc"

Notice that GoodWindowsExec uses the os.name system property to determine which Windows operating system you are running -- and thus determine the appropriate command interpreter. After executing the command interpreter, handle the standard error and standard input streams with the StreamGobbler class. StreamGobbler empties any stream passed into it in a separate thread. The class uses a simple String type to denote the stream it empties when it prints the line just read to the console.

Thus, to avoid the third pitfall related to Runtime.exec(), do not assume that a command is an executable program; know whether you are executing a standalone executable or an interpreted command. At the end of this section, I will demonstrate a simple command-line tool that will help you with that analysis.

It is important to note that the method used to obtain a process's output stream is called getInputStream(). The thing to remember is that the API sees things from the perspective of the Java program and not the external process. Therefore, the external program's output is the Java program's input. And that logic carries over to the external program's input stream, which is an output stream to the Java program.
Runtime.exec() is not a command line

One final pitfall to cover with Runtime.exec() is mistakenly assuming that exec() accepts any String that your command line (or shell) accepts. Runtime.exec() is much more limited and not cross-platform. This pitfall is caused by users attempting to use the exec() method to accept a single String as a command line would. The confusion may be due to the fact that command is the parameter name for the exec() method. Thus, the programmer incorrectly associates the parameter command with anything that he or she can type on a command line, instead of associating it with a single program and its arguments. In listing 4.6 below, a user tries to execute a command and redirect its output in one call to exec():

Listing 4.6 BadWinRedirect.javaRunning GoodWindowsExec with the dir command generates:

E:\classes\com\javaworld\jpitfalls\article2>java GoodWindowsExec "dir *.java"
Execing cmd.exe /C dir *.java
OUTPUT> Volume in drive E has no label.
OUTPUT> Volume Serial Number is 5C5F-0CC9
OUTPUT>
OUTPUT> Directory of E:\classes\com\javaworld\jpitfalls\article2
OUTPUT>
OUTPUT>10/23/00  09:01p                   805 BadExecBrowser.java
OUTPUT>10/22/00  09:35a                   770 BadExecBrowser1.java
OUTPUT>10/24/00  08:45p                   488 BadExecJavac.java
OUTPUT>10/24/00  08:46p                   519 BadExecJavac2.java
OUTPUT>10/24/00  09:13p                   930 BadExecWinDir.java
OUTPUT>10/22/00  09:21a                 2,282 BadURLPost.java
OUTPUT>10/22/00  09:20a                 2,273 BadURLPost1.java
... (some output omitted for brevity)
OUTPUT>10/12/00  09:29p                   151 SuperFrame.java
OUTPUT>10/24/00  09:23p                 1,814 TestExec.java
OUTPUT>10/09/00  05:47p                23,543 TestStringReplace.java
OUTPUT>10/12/00  08:55p                   228 TopLevel.java
OUTPUT>              22 File(s)         46,661 bytes
OUTPUT>                         19,678,420,992 bytes free
ExitValue: 0

Running GoodWindowsExec with any associated document type will launch the application associated with that document type. For example, to launch Microsoft Word to display a Word document (i.e., one with a .doc extension), type:

>java GoodWindowsExec "yourdoc.doc"

Notice that GoodWindowsExec uses the os.name system property to determine which Windows operating system you are running -- and thus determine the appropriate command interpreter. After executing the command interpreter, handle the standard error and standard input streams with the StreamGobbler class. StreamGobbler empties any stream passed into it in a separate thread. The class uses a simple String type to denote the stream it empties when it prints the line just read to the console.

Thus, to avoid the third pitfall related to Runtime.exec(), do not assume that a command is an executable program; know whether you are executing a standalone executable or an interpreted command. At the end of this section, I will demonstrate a simple command-line tool that will help you with that analysis.

It is important to note that the method used to obtain a process's output stream is called getInputStream(). The thing to remember is that the API sees things from the perspective of the Java program and not the external process. Therefore, the external program's output is the Java program's input. And that logic carries over to the external program's input stream, which is an output stream to the Java program.
Runtime.exec() is not a command line

One final pitfall to cover with Runtime.exec() is mistakenly assuming that exec() accepts any String that your command line (or shell) accepts. Runtime.exec() is much more limited and not cross-platform. This pitfall is caused by users attempting to use the exec() method to accept a single String as a command line would. The confusion may be due to the fact that command is the parameter name for the exec() method. Thus, the programmer incorrectly associates the parameter command with anything that he or she can type on a command line, instead of associating it with a single program and its arguments. In listing 4.6 below, a user tries to execute a command and redirect its output in one call to exec():

Listing 4.6 BadWinRedirect.java

 

import java.util.*;
import java.io.*;
// StreamGobbler omitted for brevity
public class BadWinRedirect
{
    public static void main(String args[])
    {
        try
        {            
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec("java jecho 'Hello World' > test.txt");
            // any error message?
            StreamGobbler errorGobbler = new 
                StreamGobbler(proc.getErrorStream(), "ERROR");            
            
            // any output?
            StreamGobbler outputGobbler = new 
                StreamGobbler(proc.getInputStream(), "OUTPUT");
                
            // kick them off
            errorGobbler.start();
            outputGobbler.start();
                                    
            // any error???
            int exitVal = proc.waitFor();
            System.out.println("ExitValue: " + exitVal);        
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}

 

Running BadWinRedirect produces:

E:\classes\com\javaworld\jpitfalls\article2>java BadWinRedirect
OUTPUT>'Hello World' > test.txt
ExitValue: 0

The program BadWinRedirect attempted to redirect the output of an echo program's simple Java version into the file test.txt. However, we find that the file test.txt does not exist. The jecho program simply takes its command-line arguments and writes them to the standard output stream. (You will find the source for jecho in the source code available for download in Resources.) In Listing 4.6, the user assumed that you could redirect standard output into a file just as you could on a DOS command line. Nevertheless, you do not redirect the output through this approach. The incorrect assumption here is that the exec() method acts like a shell interpreter; it does not. Instead, exec() executes a single executable (a program or script). If you want to process the stream to either redirect it or pipe it into another program, you must do so programmatically, using the java.io package. Listing 4.7 properly redirects the standard output stream of the jecho process into a file.

Listing 4.7 GoodWinRedirect.java

 

import java.util.*;
import java.io.*;
class StreamGobbler extends Thread
{
    InputStream is;
    String type;
    OutputStream os;
    
    StreamGobbler(InputStream is, String type)
    {
        this(is, type, null);
    }
    StreamGobbler(InputStream is, String type, OutputStream redirect)
    {
        this.is = is;
        this.type = type;
        this.os = redirect;
    }
    
    public void run()
    {
        try
        {
            PrintWriter pw = null;
            if (os != null)
                pw = new PrintWriter(os);
                
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String line=null;
            while ( (line = br.readLine()) != null)
            {
                if (pw != null)
                    pw.println(line);
                System.out.println(type + ">" + line);    
            }
            if (pw != null)
                pw.flush();
        } catch (IOException ioe)
            {
            ioe.printStackTrace();  
            }
    }
}
public class GoodWinRedirect
{
    public static void main(String args[])
    {
        if (args.length < 1)
        {
            System.out.println("USAGE java GoodWinRedirect <outputfile>");
            System.exit(1);
        }
        
        try
        {            
            FileOutputStream fos = new FileOutputStream(args[0]);
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec("java jecho 'Hello World'");
            // any error message?
            StreamGobbler errorGobbler = new 
                StreamGobbler(proc.getErrorStream(), "ERROR");            
            
            // any output?
            StreamGobbler outputGobbler = new 
                StreamGobbler(proc.getInputStream(), "OUTPUT", fos);
                
            // kick them off
            errorGobbler.start();
            outputGobbler.start();
                                    
            // any error???
            int exitVal = proc.waitFor();
            System.out.println("ExitValue: " + exitVal);
            fos.flush();
            fos.close();        
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}

 Running GoodWinRedirect produces:

E:\classes\com\javaworld\jpitfalls\article2>java GoodWinRedirect test.txt
OUTPUT>'Hello World'
ExitValue: 0

After running GoodWinRedirect, test.txt does exist. The solution to the pitfall was to simply control the redirection by handling the external process's standard output stream separately from the Runtime.exec() method. We create a separate OutputStream, read in the filename to which we redirect the output, open the file, and write the output that we receive from the spawned process's standard output to the file. Listing 4.7 completes that task by adding a new constructor to our StreamGobbler class. The new constructor takes three arguments: the input stream to gobble, the type String that labels the stream we are gobbling, and the output stream to which we redirect the input. This new version of StreamGobbler does not break any of the code in which it was previously used, as we have not changed the existing public API -- we only extended it.

Since the argument to Runtime.exec() is dependent on the operating system, the proper commands to use will vary from one OS to another. So, before finalizing arguments to Runtime.exec() and writing the code, quickly test the arguments. Listing 4.8 is a simple command-line utility that allows you to do just that.

Here's a useful exercise: try to modify TestExec to redirect the standard input or standard output to a file. When executing the javac compiler on Windows 95 or Windows 98, that would solve the problem of error messages scrolling off the top of the limited command-line buffer.

Listing 4.8 TestExec.java

 

import java.util.*;
import java.io.*;
// class StreamGobbler omitted for brevity
public class TestExec
{
    public static void main(String args[])
    {
        if (args.length < 1)
        {
            System.out.println("USAGE: java TestExec \"cmd\"");
            System.exit(1);
        }
        
        try
        {
            String cmd = args[0];
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec(cmd);
            
            // any error message?
            StreamGobbler errorGobbler = new 
                StreamGobbler(proc.getErrorStream(), "ERR");            
            
            // any output?
            StreamGobbler outputGobbler = new 
                StreamGobbler(proc.getInputStream(), "OUT");
                
            // kick them off
            errorGobbler.start();
            outputGobbler.start();
                                    
            // any error???
            int exitVal = proc.waitFor();
            System.out.println("ExitValue: " + exitVal);
        } catch (Throwable t)
          {
            t.printStackTrace();
          }
    }
}
 

Running TestExec to launch the Netscape browser and load the Java help documentation produces:

E:\classes\com\javaworld\jpitfalls\article2>java TestExec "e:\java\docs\index.html"
java.io.IOException: CreateProcess: e:\java\docs\index.html error=193
        at java.lang.Win32Process.create(Native Method)
        at java.lang.Win32Process.<init>(Unknown Source)
        at java.lang.Runtime.execInternal(Native Method)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at TestExec.main(TestExec.java:45)

Our first test failed with an error of 193. The Win32 error for value 193 is "not a valid Win32 application." This error tells us that no path to an associated application (e.g., Netscape) exists, and that the process cannot run an HTML file without an associated application.

Therefore, we try the test again, this time giving it a full path to Netscape. (Alternately, we could add Netscape to our PATH environment variable.) A second run of TestExec produces:

E:\classes\com\javaworld\jpitfalls\article2>java TestExec
"e:\program files\netscape\program\netscape.exe e:\java\docs\index.html"
ExitValue: 0

This worked! The Netscape browser launches, and it then loads the Java help documentation.

One additional improvement to TestExec would include a command-line switch to accept input from standard input. You would then use the Process.getOutputStream() method to pass the input to the spawned external program.

To sum up, follow these rules of thumb to avoid the pitfalls in Runtime.exec():

   1. You cannot obtain an exit status from an external process until it has exited
   2. You must immediately handle the input, output, and error streams from your spawned external process
   3. You must use Runtime.exec() to execute programs
   4. You cannot use Runtime.exec() like a command line

Correction to Pitfall 3

In the discussion of Pitfall 3 ("Don't mix floats and doubles when generating text or XML messages") in my last column, I incorrectly stated that the different string representation of a decimal number after casting it from a float to a double was a bug. While this is a pitfall, its cause is not a bug, but the fact that the decimal numbers in question -- 100.28 and 91.09 -- do not represent precisely in binary. I'd like to thank Thomas Okken and the others who straightened me out. If you enjoy discussing the finer points of numerical methods, you can email Thomas.

The combination of forgetting my numerical methods class, the numerous bug reports on the bug parade, and the automatic rounding of floats and doubles when printing (but not after casting a float to a double) threw me. I apologize for confusing anyone who read the article, especially to new Java programmers. I present two better solutions to the problem:

The first possible solution is to always specify the desired rounding explicitly with NumberFormat. In my case, I use the float and double to represent dollars and cents; therefore, I need only two significant digits. Listing C3.1 demonstrates how to use the NumberFormat class to specify a maximum of two fraction digits.

Listing C3.1 FormatNumbers.java

import java.text.*;
public class FormatNumbers
{
    public static void main(String [] args)
    {
        try
        {
            NumberFormat fmt = NumberFormat.getInstance();
            fmt.setMaximumFractionDigits(2);
            float f = 100.28f;
            System.out.println("As a float        : " + f);
            double d = f;
            System.out.println("Cast to a double  : " + d);
            System.out.println("Using NumberFormat: " + fmt.format(d));            
        } catch (Throwable t)
          {
            t.printStackTrace();
          }          
    }
}
 

When we run the FormatNumbers program, it produces:

E:\classes\com\javaworld\jpitfalls\article2>java FormatNumbers
As a float        : 100.28
Cast to a double  : 100.27999877929688
Using NumberFormat: 100.28

As you can see -- regardless of whether we cast the float to a double -- when we specify the number of digits we want, it properly rounds to that precision -- even if the number is infinitely repeating in binary. To circumvent this pitfall, control the formatting of your doubles and floats when converting to a String.

A second, simpler solution would be to not use a float to represent cents. Integers (number of pennies) can represent cents, with a legal range of 0 to 99. You can check the range in the mutator method.
Next time

In my next column, I'll present another pitfall from java.lang, as well as two traps hiding in the java.net and the Swing packages. If you know of any Java pitfalls that have wasted your time and caused you frustration, please email them to me so we can save others the same fate.

分享到:
评论

相关推荐

    when Runtime.exec&#40;&#41; won't 中文翻译版

    ### Java中的Runtime.exec()陷阱详解 #### 引言 在Java编程中,`java.lang.Runtime` 类提供了与Java虚拟机的运行时环境交互的方法,其中`Runtime.exec()` 方法尤其受到开发者的青睐,因为它允许Java程序启动和控制...

    ICS delphixe10源码版

    all developers having contributed (When no name is given, the change is by F. Piette). I can't list all contributors here but I want to specially thanks two specially active contributors: - Arno ...

    Matlab环境下决策分类树的构建、优化与应用

    内容概要:本文详细介绍了如何利用Matlab构建、优化和应用决策分类树。首先,讲解了数据准备阶段,将数据与程序分离,确保灵活性。接着,通过具体实例展示了如何使用Matlab内置函数如fitctree快速构建决策树模型,并通过可视化工具直观呈现决策树结构。针对可能出现的过拟合问题,提出了基于成本复杂度的剪枝方法,以提高模型的泛化能力。此外,还分享了一些实用技巧,如处理连续特征、保存模型、并行计算等,帮助用户更好地理解和应用决策树。 适合人群:具有一定编程基础的数据分析师、机器学习爱好者及科研工作者。 使用场景及目标:适用于需要进行数据分类任务的场景,特别是当需要解释性强的模型时。主要目标是教会读者如何在Matlab环境中高效地构建和优化决策分类树,从而应用于实际项目中。 其他说明:文中不仅提供了完整的代码示例,还强调了代码模块化的重要性,便于后续维护和扩展。同时,对于初学者来说,建议从简单的鸢尾花数据集开始练习,逐步掌握决策树的各项技能。

    《营销调研》第7章-探索性调研数据采集.pptx

    《营销调研》第7章-探索性调研数据采集.pptx

    Assignment1_search_final(1).ipynb

    Assignment1_search_final(1).ipynb

    美团外卖优惠券小程序 美团优惠券微信小程序 自带流量主模式 带教程.zip

    美团优惠券小程序带举牌小人带菜谱+流量主模式,挺多外卖小程序的,但是都没有搭建教程 搭建: 1、下载源码,去微信公众平台注册自己的账号 2、解压到桌面 3、打开微信开发者工具添加小程序-把解压的源码添加进去-appid改成自己小程序的 4、在pages/index/index.js文件搜流量主广告改成自己的广告ID 5、到微信公众平台登陆自己的小程序-开发管理-开发设置-服务器域名修改成

    《计算机录入技术》第十八章-常用外文输入法.pptx

    《计算机录入技术》第十八章-常用外文输入法.pptx

    基于Andorid的跨屏拖动应用设计.zip

    基于Andorid的跨屏拖动应用设计实现源码,主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。

    《网站建设与维护》项目4-在线购物商城用户管理功能.pptx

    《网站建设与维护》项目4-在线购物商城用户管理功能.pptx

    区块链_房屋转租系统_去中心化存储_数据防篡改_智能合约_S_1744435730.zip

    区块链_房屋转租系统_去中心化存储_数据防篡改_智能合约_S_1744435730

    《计算机应用基础实训指导》实训五-Word-2010的文字编辑操作.pptx

    《计算机应用基础实训指导》实训五-Word-2010的文字编辑操作.pptx

    《移动通信(第4版)》第5章-组网技术.ppt

    《移动通信(第4版)》第5章-组网技术.ppt

    ABB机器人基础.pdf

    ABB机器人基础.pdf

    《综合布线施工技术》第9章-综合布线实训指导.ppt

    《综合布线施工技术》第9章-综合布线实训指导.ppt

    最新修复版万能镜像系统源码-最终版站群利器持续更新升级

    很不错的一套站群系统源码,后台配置采集节点,输入目标站地址即可全自动智能转换自动全站采集!支持 https、支持 POST 获取、支持搜索、支持 cookie、支持代理、支持破解防盗链、支持破解防采集 全自动分析,内外链接自动转换、图片地址、css、js,自动分析 CSS 内的图片使得页面风格不丢失: 广告标签,方便在规则里直接替换广告代码 支持自定义标签,标签可自定义内容、自由截取、内容正则截取。可以放在模板里,也可以在规则里替换 支持自定义模板,可使用标签 diy 个性模板,真正做到内容上移花接木 调试模式,可观察采集性能,便于发现和解决各种错误 多条采集规则一键切换,支持导入导出 内置强大替换和过滤功能,标签过滤、站内外过滤、字符串替换、等等 IP 屏蔽功能,屏蔽想要屏蔽 IP 地址让它无法访问 ****高级功能*****· url 过滤功能,可过滤屏蔽不采集指定链接· 伪原创,近义词替换有利于 seo· 伪静态,url 伪静态化,有利于 seo· 自动缓存自动更新,可设置缓存时间达到自动更新,css 缓存· 支持演示有阿三源码简繁体互转· 代理 IP、伪造 IP、随机 IP、伪造 user-agent、伪造 referer 来路、自定义 cookie,以便应对防采集措施· url 地址加密转换,个性化 url,让你的 url 地址与众不同· 关键词内链功能· 还有更多功能等你发现…… 程序使用非常简单,仅需在后台输入一个域名即可建站,不限子域名,站群利器,无授权,无绑定限制,使用后台功能可对页面进行自定义修改,在程序后台开启生 成功能,只要访问页面就会生成一个本地文件。当用户再次访问的时候就直接访问网站本地的页面,所以目标站点无法访问了也没关系,我们的站点依然可以访问, 支持伪静态、伪原创、生成静态文件、自定义替换、广告管理、友情链接管理、自动下载 CSS 内的图。

    《Approaching(Almost)any machine learning problem》中文版第11章

    【自然语言处理】文本分类方法综述:从基础模型到深度学习的情感分析系统设计

    基于Andorid的下拉浏览应用设计.zip

    基于Andorid的下拉浏览应用设计实现源码,主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。

    P2插电式混合动力系统Simulink模型:基于逻辑门限值控制策略的混动汽车仿真

    内容概要:本文详细介绍了一个原创的P2插电式混合动力系统Simulink模型,该模型基于逻辑门限值控制策略,涵盖了多个关键模块如工况输入、驾驶员模型、发动机模型、电机模型、制动能量回收模型、转矩分配模型、运行模式切换模型、档位切换模型以及纵向动力学模型。模型支持多种标准工况(WLTC、UDDS、EUDC、NEDC)和自定义工况,并展示了丰富的仿真结果,包括发动机和电机转矩变化、工作模式切换、档位变化、电池SOC变化、燃油消耗量、速度跟随和最大爬坡度等。此外,文章还深入探讨了逻辑门限值控制策略的具体实现及其效果,提供了详细的代码示例和技术细节。 适合人群:汽车工程专业学生、研究人员、混动汽车开发者及爱好者。 使用场景及目标:①用于教学和科研,帮助理解和掌握P2混动系统的原理和控制策略;②作为开发工具,辅助设计和优化混动汽车控制系统;③提供仿真平台,评估不同工况下的混动系统性能。 其他说明:文中不仅介绍了模型的整体架构和各模块的功能,还分享了许多实用的调试技巧和优化方法,使读者能够更好地理解和应用该模型。

    电力系统分布式调度中ADMM算法的MATLAB实现及其应用

    内容概要:本文详细介绍了基于ADMM(交替方向乘子法)算法在电力系统分布式调度中的应用,特别是并行(Jacobi)和串行(Gauss-Seidel)两种不同更新模式的实现。文中通过MATLAB代码展示了这两种模式的具体实现方法,并比较了它们的优劣。并行模式适用于多核计算环境,能够充分利用硬件资源,尽管迭代次数较多,但总体计算时间较短;串行模式则由于“接力式”更新机制,通常收敛更快,但在计算资源有限的情况下可能会形成瓶颈。此外,文章还讨论了惩罚系数rho的自适应调整策略以及在电-气耦合系统优化中的应用实例。 适合人群:从事电力系统优化、分布式计算研究的专业人士,尤其是有一定MATLAB编程基础的研究人员和技术人员。 使用场景及目标:①理解和实现ADMM算法在电力系统分布式调度中的应用;②评估并行和串行模式在不同应用场景下的性能表现;③掌握惩罚系数rho的自适应调整技巧,提高算法收敛速度和稳定性。 其他说明:文章提供了详细的MATLAB代码示例,帮助读者更好地理解和实践ADMM算法。同时,强调了在实际工程应用中需要注意的关键技术和优化策略。

    这篇文章详细探讨了交错并联Buck变换器的设计、仿真及其实现,涵盖了从理论分析到实际应用的多个方面(含详细代码及解释)

    内容概要:本文深入研究了交错并联Buck变换器的工作原理、性能优势及其具体实现。文章首先介绍了交错并联Buck变换器相较于传统Buck变换器的优势,包括减小输出电流和电压纹波、降低开关管和二极管的电流应力、减小输出滤波电容容量等。接着,文章详细展示了如何通过MATLAB/Simulink建立该变换器的仿真模型,包括参数设置、电路元件添加、PWM信号生成及连接、电压电流测量模块的添加等。此外,还探讨了PID控制器的设计与实现,通过理论分析和仿真验证了其有效性。最后,文章通过多个仿真实验验证了交错并联Buck变换器在纹波性能、器件应力等方面的优势,并分析了不同控制策略的效果,如P、PI、PID控制等。 适合人群:具备一定电力电子基础,对DC-DC变换器特别是交错并联Buck变换器感兴趣的工程师和技术人员。 使用场景及目标:①理解交错并联Buck变换器的工作原理及其相对于传统Buck变换器的优势;②掌握使用MATLAB/Simulink搭建交错并联Buck变换器仿真模型的方法;③学习PID控制器的设计与实现,了解其在电源系统中的应用;④通过仿真实验验证交错并联Buck变换器的性能,评估不同控制策略的效果。 其他说明:本文不仅提供了详细的理论分析,还给出了大量可运行的MATLAB代码,帮助读者更好地理解和实践交错并联Buck变换器的设计与实现。同时,通过对不同控制策略的对比分析,为实际工程应用提供了有价值的参考。

Global site tag (gtag.js) - Google Analytics