作为Java语言的一部分。java.lang包被隐藏的导入到每一个Java程序。这个包的表面陷阱,经常影响到大多数程序员。这个月,我将讨论运行时exec()方法时的潜伏陷阱。
陷阱4:当运行exec()时不会执行命令
java.lang.Runtime类,突出了静态方法calledgetRuntime(),,它会检索当前的Java运行时环境。这是唯一的方法来获取Runtime对象的引用。获取该引用,您通过可以调用Runtime类的exec()方法运行外部程序。开发人员经常调用这个方法来启动浏览器显示一个HTML帮助页面。
exec()有四个重载:
1 public Process exec(String command); 2 public Process exec(String [] cmdArray); 3 public Process exec(String command, String [] envp); 4 public Process exec(String [] cmdArray, String [] envp);
对于每个这样的方法,都会产生一个命令,并可能携带一组参数——被传递给一个特定操作系统的函数调用。这随后创建一个特定操作系统的进程(一个运行着的程序),procss类将持有该程序返回Java VM的引用。这个procss类是一个抽象类,具体子类的实现依赖于不同的底层操作系统。
你可以通过三种可能的输入参数到这些方法:
1、一个字符串,表示程序执行和程序的任何参数。
2、一个字符串数组,通过参数来区分出程序的实现功能。
3、一个环境变量的数组
传递环境变量是,使用格式化的方式:名称=值。如果你使用单个字符串和它的参数的方式调用exec()的重载,,注意字符串是通过StringTokenizer类被解析,使用空格作为分隔符。
陷入 IllegalThreadStateException
1 import java.util.*; 2 import java.io.*; 3 public class BadExecJavac 4 { 5 public static void main(String args[]) 6 { 7 try 8 { 9 Runtime rt = Runtime.getRuntime(); 10 Process proc = rt.exec("javac"); 11 int exitVal = proc.exitValue(); 12 System.out.println("Process exitValue: " + exitVal); 13 } catch (Throwable t) 14 { 15 t.printStackTrace(); 16 } 17 } 18 }
运行的BadExecJavac产生:
1 E:\classes\com\javaworld\jpitfalls\article2>java BadExecJavac 2 java.lang.IllegalThreadStateException: process has not exited 3 at java.lang.Win32Process.exitValue(Native Method) 4 at BadExecJavac.main(BadExecJavac.java:13)
1 import java.util.*; 2 import java.io.*; 3 public class BadExecJavac2 4 { 5 public static void main(String args[]) 6 { 7 try 8 { 9 Runtime rt = Runtime.getRuntime(); 10 Process proc = rt.exec("javac"); 11 int exitVal = proc.waitFor(); 12 System.out.println("Process exitValue: " + exitVal); 13 } catch (Throwable t) 14 { 15 t.printStackTrace(); 16 } 17 } 18 }
为什么 Runtime.exec() 挂起
因为一些本机平台只提供有限的缓冲区大小为标准输入和输出流,未能及时写输入流或读取输出流的子流程可能会导致子流程阻止,甚至死锁。
1 import java.util.*; 2 import java.io.*; 3 public class MediocreExecJavac 4 { 5 public static void main(String args[]) 6 { 7 try 8 { 9 Runtime rt = Runtime.getRuntime(); 10 Process proc = rt.exec("javac"); 11 InputStream stderr = proc.getErrorStream(); 12 InputStreamReader isr = new InputStreamReader(stderr); 13 BufferedReader br = new BufferedReader(isr); 14 String line = null; 15 System.out.println("<ERROR>"); 16 while ( (line = br.readLine()) != null) 17 System.out.println(line); 18 System.out.println("</ERROR>"); 19 int exitVal = proc.waitFor(); 20 System.out.println("Process exitValue: " + exitVal); 21 } catch (Throwable t) 22 { 23 t.printStackTrace(); 24 } 25 } 26 }
运行MediocreExecJava
产生:
1 E:\classes\com\javaworld\jpitfalls\article2>java MediocreExecJavac 2 <ERROR> 3 Usage: javac <options> <source files> 4 where <options> includes: 5 -g Generate all debugging info 6 -g:none Generate no debugging info 7 -g:{lines,vars,source} Generate only some debugging info 8 -O Optimize; may hinder debugging or enlarge class files 9 -nowarn Generate no warnings 10 -verbose Output messages about what the compiler is doing 11 -deprecation Output source locations where deprecated APIs are used 12 -classpath <path> Specify where to find user class files 13 -sourcepath <path> Specify where to find input source files 14 -bootclasspath <path> Override location of bootstrap class files 15 -extdirs <dirs> Override location of installed extensions 16 -d <directory> Specify where to place generated class files 17 -encoding <encoding> Specify character encoding used by source files 18 -target <release> Generate class files for specific VM version 19 </ERROR> 20 Process exitValue: 2
所以,MediocreExecJavac运行产生一个退出值2。通常,一个退出值0表示成功,任何非零值表示一个错误。这些退出值的含义取决于特定的操作系统。一个Win32错误值为2是一个“未找到文件”错误。这是有道理的,因为javac期望我们遵循的程序源代码文件进行编译。
假设一个命令是一个可执行程序
1 import java.util.*; 2 import java.io.*; 3 public class BadExecWinDir 4 { 5 public static void main(String args[]) 6 { 7 try 8 { 9 Runtime rt = Runtime.getRuntime(); 10 Process proc = rt.exec("dir"); 11 InputStream stdin = proc.getInputStream(); 12 InputStreamReader isr = new InputStreamReader(stdin); 13 BufferedReader br = new BufferedReader(isr); 14 String line = null; 15 System.out.println("<OUTPUT>"); 16 while ( (line = br.readLine()) != null) 17 System.out.println(line); 18 System.out.println("</OUTPUT>"); 19 int exitVal = proc.waitFor(); 20 System.out.println("Process exitValue: " + exitVal); 21 } catch (Throwable t) 22 { 23 t.printStackTrace(); 24 } 25 } 26 }
运行BadExecWinDir输出:
1 E:\classes\com\javaworld\jpitfalls\article2>java BadExecWinDir 2 java.io.IOException: CreateProcess: dir error=2 3 at java.lang.Win32Process.create(Native Method) 4 at java.lang.Win32Process.<init>(Unknown Source) 5 at java.lang.Runtime.execInternal(Native Method) 6 at java.lang.Runtime.exec(Unknown Source) 7 at java.lang.Runtime.exec(Unknown Source) 8 at java.lang.Runtime.exec(Unknown Source) 9 at java.lang.Runtime.exec(Unknown Source) 10 at BadExecWinDir.main(BadExecWinDir.java:12)
如前所述,误差值为2的意思是“未找到文件”,在这种情况下,意味着可执行文件名为dir.exe不能被发现。这是因为目录命令是Windows命令解释器的一部分,而不是一个单独的可执行文件。要运行Windows命令解释器,执行orcmd.exe或者command.com,这取决于您使用的Windows操作系统。清单4.5运行的一个Windows命令解释器,然后执行用户提供的命令(如。,dir)。
清单 4.5 GoodWindowsExec.java
1 import java.util.*; 2 import java.io.*; 3 class StreamGobbler extends Thread 4 { 5 InputStream is; 6 String type; 7 8 StreamGobbler(InputStream is, String type) 9 { 10 this.is = is; 11 this.type = type; 12 } 13 14 public void run() 15 { 16 try 17 { 18 InputStreamReader isr = new InputStreamReader(is); 19 BufferedReader br = new BufferedReader(isr); 20 String line=null; 21 while ( (line = br.readLine()) != null) 22 System.out.println(type + ">" + line); 23 } catch (IOException ioe) 24 { 25 ioe.printStackTrace(); 26 } 27 } 28 } 29 public class GoodWindowsExec 30 { 31 public static void main(String args[]) 32 { 33 if (args.length < 1) 34 { 35 System.out.println("USAGE: java GoodWindowsExec <cmd>"); 36 System.exit(1); 37 } 38 39 try 40 { 41 String osName = System.getProperty("os.name" ); 42 String[] cmd = new String[3]; 43 if( osName.equals( "Windows NT" ) ) 44 { 45 cmd[0] = "cmd.exe" ; 46 cmd[1] = "/C" ; 47 cmd[2] = args[0]; 48 } 49 else if( osName.equals( "Windows 95" ) ) 50 { 51 cmd[0] = "command.com" ; 52 cmd[1] = "/C" ; 53 cmd[2] = args[0]; 54 } 55 56 Runtime rt = Runtime.getRuntime(); 57 System.out.println("Execing " + cmd[0] + " " + cmd[1] 58 + " " + cmd[2]); 59 Process proc = rt.exec(cmd); 60 // any error message? 61 StreamGobbler errorGobbler = new 62 StreamGobbler(proc.getErrorStream(), "ERROR"); 63 64 // any output? 65 StreamGobbler outputGobbler = new 66 StreamGobbler(proc.getInputStream(), "OUTPUT"); 67 68 // kick them off 69 errorGobbler.start(); 70 outputGobbler.start(); 71 72 // any error??? 73 int exitVal = proc.waitFor(); 74 System.out.println("ExitValue: " + exitVal); 75 } catch (Throwable t) 76 { 77 t.printStackTrace(); 78 } 79 } 80 }
使用dir命令,运行GoodWindowsExec产生:
1 E:\classes\com\javaworld\jpitfalls\article2>java GoodWindowsExec "dir *.java" 2 Execing cmd.exe /C dir *.java 3 OUTPUT> Volume in drive E has no label. 4 OUTPUT> Volume Serial Number is 5C5F-0CC9 5 OUTPUT> 6 OUTPUT> Directory of E:\classes\com\javaworld\jpitfalls\article2 7 OUTPUT> 8 OUTPUT>10/23/00 09:01p 805 BadExecBrowser.java 9 OUTPUT>10/22/00 09:35a 770 BadExecBrowser1.java 10 OUTPUT>10/24/00 08:45p 488 BadExecJavac.java 11 OUTPUT>10/24/00 08:46p 519 BadExecJavac2.java 12 OUTPUT>10/24/00 09:13p 930 BadExecWinDir.java 13 OUTPUT>10/22/00 09:21a 2,282 BadURLPost.java 14 OUTPUT>10/22/00 09:20a 2,273 BadURLPost1.java 15 ... (some output omitted for brevity) 16 OUTPUT>10/12/00 09:29p 151 SuperFrame.java 17 OUTPUT>10/24/00 09:23p 1,814 TestExec.java 18 OUTPUT>10/09/00 05:47p 23,543 TestStringReplace.java 19 OUTPUT>10/12/00 08:55p 228 TopLevel.java 20 OUTPUT> 22 File(s) 46,661 bytes 21 OUTPUT> 19,678,420,992 bytes free 22 ExitValue: 0
GoodWindowsExec运行与任何相关的文档类型将启动与之关联文档类型的应用程序。例如,启动Microsoft Word来显示一个Word文档(即一个带doc扩展名的文件)。
>java GoodWindowsExec "yourdoc.doc"
在执行命令解释器,使用StreamGobbler类来处理标准错误和标准输入流。StreamGobbler通过独立线程,来清空任何传递到它的流。这个类使用一个简单的字符串类型来表示流清空,在当它将打印行输出到控制台时起作用。
因此,为了避免运行exec()的第三个陷阱,首先不要认为一个命令是一个可执行程序;其次要了解你是否正在执行一个独立的可执行文件或一种解释命令。在结束这一节中,我将演示一个简单的命令行工具,可以帮你分析。
值得注意的是,用于获取一个进程的输出流的方法叫做getInputStream()。要记住的是,API看待对象的角度,是从Java程序内部,而不是外部的过程。因此,外部程序的输出是Java程序的输入。这种逻辑,也表明外部程序的输入流,是一个Java程序的输出流。
Runtime.exec()不是命令行
1 import java.util.*; 2 import java.io.*; 3 // StreamGobbler omitted for brevity 4 public class BadWinRedirect 5 { 6 public static void main(String args[]) 7 { 8 try 9 { 10 Runtime rt = Runtime.getRuntime(); 11 Process proc = rt.exec("java jecho 'Hello World' > test.txt"); 12 // any error message? 13 StreamGobbler errorGobbler = new 14 StreamGobbler(proc.getErrorStream(), "ERROR"); 15 16 // any output? 17 StreamGobbler outputGobbler = new 18 StreamGobbler(proc.getInputStream(), "OUTPUT"); 19 20 // kick them off 21 errorGobbler.start(); 22 outputGobbler.start(); 23 24 // any error??? 25 int exitVal = proc.waitFor(); 26 System.out.println("ExitValue: " + exitVal); 27 } catch (Throwable t) 28 { 29 t.printStackTrace(); 30 } 31 } 32 }
E:\classes\com\javaworld\jpitfalls\article2>java BadWinRedirect OUTPUT>'Hello World' > test.txt ExitValue: 0
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 产生:
1 E:\classes\com\javaworld\jpitfalls\article2>java GoodWinRedirect test.txt 2 OUTPUT>'Hello World' 3 ExitValue: 0
因为参数来运行exec()是依赖于操作系统、不同系统之间的适用命令会有所变化。所以,在最终确定Runtime.exec()参数和编写代码,快速测试。清单4.8是一个简单的命令行工具,允许你这样做。
这是一个有用的练习:尝试修改TestExec重定向标准输入和标准输出到一个文件。当在Windows 95或Windows 98上执行javac编译器,这将解决错误消息超出命令行有限缓存的问题。
1 import java.util.*; 2 import java.io.*; 3 // class StreamGobbler omitted for brevity 4 public class TestExec 5 { 6 public static void main(String args[]) 7 { 8 if (args.length < 1) 9 { 10 System.out.println("USAGE: java TestExec \"cmd\""); 11 System.exit(1); 12 } 13 14 try 15 { 16 String cmd = args[0]; 17 Runtime rt = Runtime.getRuntime(); 18 Process proc = rt.exec(cmd); 19 20 // any error message? 21 StreamGobbler errorGobbler = new 22 StreamGobbler(proc.getErrorStream(), "ERR"); 23 24 // any output? 25 StreamGobbler outputGobbler = new 26 StreamGobbler(proc.getInputStream(), "OUT"); 27 28 // kick them off 29 errorGobbler.start(); 30 outputGobbler.start(); 31 32 // any error??? 33 int exitVal = proc.waitFor(); 34 System.out.println("ExitValue: " + exitVal); 35 } catch (Throwable t) 36 { 37 t.printStackTrace(); 38 } 39 } 40 }
运行TestExec 启动Netscape浏览器和加载Java帮助文档 产生:
1 E:\classes\com\javaworld\jpitfalls\article2>java TestExec "e:\java\docs\index.html" 2 java.io.IOException: CreateProcess: e:\java\docs\index.html error=193 3 at java.lang.Win32Process.create(Native Method) 4 at java.lang.Win32Process.<init>(Unknown Source) 5 at java.lang.Runtime.execInternal(Native Method) 6 at java.lang.Runtime.exec(Unknown Source) 7 at java.lang.Runtime.exec(Unknown Source) 8 at java.lang.Runtime.exec(Unknown Source) 9 at java.lang.Runtime.exec(Unknown Source) 10 at TestExec.main(TestExec.java:45)
我们的第一个测试失败,错误193。Win32误差值193“不是一个有效的Win32应用程序。“这个错误告诉我们,没有找到关联的应用程序(如网景浏览器)存在,而且这一流程不能运行一个HTML文件没有关联的应用程序。
因此,我们尝试测试,这次又给它一个完整路径网景。(或者,我们可以添加到我们的PATH environment网景变量)。第二次运行的TestExec产生:
1 E:\classes\com\javaworld\jpitfalls\article2>java TestExec 2 "e:\program files\netscape\program\netscape.exe e:\java\docs\index.html" 3 ExitValue: 0
一个额外的改进包括一个命令行开关,使TestExec接受从标准输入的输入流。然后您将使用Process.getOutputStream()方法通过输入派生外部程序。
你必须从你外部程序立即处理输入、输出和错误流
您必须使用运行时exec()来执行程序
你不能使用运行时执行()就像一个命令行
原文地址:http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4
参考地址:https://blog.csdn.net/vernonzheng/article/details/8221452