转自:http://blog.csdn.net/vernonzheng/article/details/8221452
原文:http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html
作为Java语言的一部分。java.lang包被隐藏的导入到每一个Java程序。这个包的表面陷阱,经常影响到大多数程序员。这个月,我将讨论运行时exec()方法时的潜伏陷阱。
陷阱4:当运行exec()不会
java.lang.Runtime类,突出了静态方法calledgetRuntime(),,它会检索当前的Java运行时环境。这是唯一的方法来获取Runtime对象的引用。获取该引用,您通过可以调用Runtime类的exec()方法运行外部程序。开发人员经常调用这个方法来启动浏览器显示一个HTML帮助页面。
exec()有四个重载:
- <span style="font-family:SimSun;">public Process exec(String command);
- public Process exec(String [] cmdArray);
- public Process exec(String command, String [] envp);
- public Process exec(String [] cmdArray, String [] envp); </span>
对于每个这样的方法,都会产生一个命令,并可能携带一组参数——被传递给一个特定操作系统的函数调用。这随后创建一个特定操作系统的进程(一个运行着的程序),procss类将持有该程序返回Java VM的引用。这个procss类是一个抽象类,具体子类的实现依赖于不同的底层操作系统。
你可以通过三种可能的输入参数到这些方法:
1、一个字符串,表示程序执行和程序的任何参数。
2、一个字符串数组,通过参数来区分出程序的实现功能。
3、一个环境变量的数组
传递环境变量是,使用格式化的方式:名称=值。如果你使用单个字符串和它的参数的方式调用exec()的重载,,注意字符串是通过StringTokenizer类被解析,使用空格作为分隔符。
陷入 IllegalThreadStateException
exe)。
- <span style="font-family:SimSun;">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();
- }
- }
- }</span>
运行的BadExecJavac产生:
- <span style="font-family:SimSun;">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)</span>
如果一个外部进程尚未完成,exitValue()方法将抛出IllegalThreadStateException。这就是程序失败的原因。尽管文档声明了这个事实,为什么这个方法不能等待一个有效性的结果返回?
- <span style="font-family:SimSun;">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();
- }
- }
- }</span>
不幸的是,一个运行的BadExecJavac2不产生任何输出。程序挂起、一直未完成。为什么javac进程一直没有完成?
为什么 Runtime.exec() 挂起
- <span style="font-size: 14px; "><span style="font-family: SimSun; "><strong><span style="color: rgb(255, 0, 0); ">因为一些本机平台只提供有限的缓冲区大小为标准输入和输出流,未能及时写输入流或读取输出流的子流程可能会导致子流程阻止,甚至死锁。</span></strong></span></span>
- <span style="font-family:SimSun;">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();
- }
- }
- }</span>
运行MediocreExecJava
产生:
- <span style="font-family:SimSun;">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</span>
所以,MediocreExecJavac运行产生一个退出值2。通常,一个退出值0表示成功,任何非零值表示一个错误。这些退出值的含义取决于特定的操作系统。一个Win32错误值为2是一个“未找到文件”错误。这是有道理的,因为javac期望我们遵循的程序源代码文件进行编译。
假设一个命令是一个可执行程序
- <span style="font-family:SimSun;">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();
- }
- }
- }</span>
运行BadExecWinDir输出:
- <span style="font-family:SimSun;">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)</span>
如前所述,误差值为2的意思是“未找到文件”,在这种情况下,意味着可执行文件名为dir.exe不能被发现。这是因为目录命令是Windows命令解释器的一部分,而不是一个单独的可执行文件。要运行Windows命令解释器,执行orcmd.exe或者command.com,这取决于您使用的Windows操作系统。清单4.5运行的一个Windows命令解释器,然后执行用户提供的命令(如。,dir)。
清单 4.5 GoodWindowsExec.java
- <span style="font-family:SimSun;">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();
- }
- }
- }</span>
使用dir命令,运行GoodWindowsExec产生:
- <span style="font-family:SimSun;">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</span>
GoodWindowsExec运行与任何相关的文档类型将启动与之关联文档类型的应用程序。例如,启动Microsoft Word来显示一个Word文档(即一个带doc扩展名的文件)。
- >java GoodWindowsExec "yourdoc.doc"
注意,GoodWindowsExec使用操作系统的系统名字属性来确定,您运行的是哪种Windows操作系统,从而确定适当的命令解释器。
在执行命令解释器,使用StreamGobbler类来处理标准错误和标准输入流。StreamGobbler通过独立线程,来清空任何传递到它的流。这个类使用一个简单的字符串类型来表示流清空,在当它将打印行输出到控制台时起作用。
因此,为了避免运行exec()的第三个陷阱,首先不要认为一个命令是一个可执行程序;其次要了解你是否正在执行一个独立的可执行文件或一种解释命令。在结束这一节中,我将演示一个简单的命令行工具,可以帮你分析。
值得注意的是,用于获取一个进程的输出流的方法叫做getInputStream()。要记住的是,API看待对象的角度,是从Java程序内部,而不是外部的过程。因此,外部程序的输出是Java程序的输入。这种逻辑,也表明外部程序的输入流,是一个Java程序的输出流。
Runtime.exec()不是命令行
- 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();
- }
- }
- }
运行BadWinRedirect 产生:
E:\classes\com\javaworld\jpitfalls\article2>java BadWinRedirect
OUTPUT>'Hello World' > test.txt
ExitValue: 0
该程序BadWinRedirect试图重定向Java版本的echo程序输出到文件test.txt。 然而,我们发现文件test.txt并不存在。jecho的程序只需要它的命令行参数,并将其写入到标准输出流。(你会发现jecho的源代码在源代码可供下载inResources.)清单4.6,用户认为你可以重定向标准输出到一个文件中,就像你可能在一个DOS命令行中执行一样。然而,你不能通过这种方法重定向输出。这里不正确的假设是,exec()方法会像一个shell解析器一样,但是它不会。相反,exec()执行一个单一可执行文件(一个程序或脚本)。如果你想处理流重定向或管道,要么用另一个程序实现,你必须通过编程,使用java。io包。清单4.7
完成重定向标准输出流的jecho流程到一个文件中。
- 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();
- }
- }
- }
运行 GoodWinRedirect 产生:
- E:\classes\com\javaworld\jpitfalls\article2>java GoodWinRedirect test.txt
- OUTPUT>'Hello World'
- ExitValue: 0
在运行GoodWinRedirect、test.txt确实存在。解决这个陷阱很简单,就是通过外部进程控制重定向的标准输出流,独立于exec()方法。我们创建一个单独的OutputStream、读取的文件名,打开文件,我们将从派生进程的标准输出写到文件。清单4.7通过对StreamGobbler类添加一个新的构造器来完成这个任务。新构造函数有三个参数:读入输入流,String类型标记我们是否正在使用读入输出流,重定向输入。这个新版本的StreamGobbler并不违反任何代码之前使用的,因为我们没有改变现有的公共API——我们只扩展它。
因为参数来运行exec()是依赖于操作系统、不同系统之间的适用命令会有所变化。所以,在最终确定Runtime.exec()参数和编写代码,快速测试。清单4.8是一个简单的命令行工具,允许你这样做。
这是一个有用的练习:尝试修改TestExec重定向标准输入和标准输出到一个文件。当在Windows 95或Windows 98上执行javac编译器,这将解决错误消息超出命令行有限缓存的问题。
- 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();
- }
- }
- }
- 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)
我们的第一个测试失败,错误193。Win32误差值193“不是一个有效的Win32应用程序。“这个错误告诉我们,没有找到关联的应用程序(如网景浏览器)存在,而且这一流程不能运行一个HTML文件没有关联的应用程序。
因此,我们尝试测试,这次又给它一个完整路径网景。(或者,我们可以添加到我们的PATH environment网景变量)。第二次运行的TestExec产生:
- E:\classes\com\javaworld\jpitfalls\article2>java TestExec
- "e:\program files\netscape\program\netscape.exe e:\java\docs\index.html"
- ExitValue: 0
ok!网景浏览器的打开,然后装入Java帮助文档。
一个额外的改进包括一个命令行开关,使TestExec接受从标准输入的输入流。然后您将使用Process.getOutputStream()方法通过输入派生外部程序。
你必须从你外部程序立即处理输入、输出和错误流
您必须使用运行时exec()来执行程序
你不能使用运行时执行()就像一个命令行