或許大部分有寫過Java程式的人都知道java.lang.Runtime這個class有一個method叫做exec(),可以被用來呼叫(調用)外部的程式。然而大部分的人都不知道這個method存在著幾個機車的小陷阱,一個不小心就會發生災難了,待我娓娓道來...

這個method有幾個overloaded的版本如下:

Process exec(String[] progArray, String[] envp, File directory)

Executes the specified command and its arguments in a separate native process.
Process exec(String prog, String[] envp)

Executes the specified program in a separate native process.
Process exec(String prog)

Executes the specified program in a separate native process.
Process exec(String prog, String[] envp, File directory)

Executes the specified program in a separate native process.
Process exec(String[] progArray)

Executes the specified command and its arguments in a separate native process.
Process exec(String[] progArray, String[] envp)

Executes the specified command and its arguments in a separate native process.

其中prog這個參數是要執行的外部可執行檔名稱,progArray則是可執行檔名稱和一些參數,當然你也可以把progArray合成一個prog參數(中間必須用空白格開),不過,問題多多。envp表示環境變數,directory表示可執行檔的目錄,更詳細的參數說明請參考Java文件。

請看以下的程式碼隱藏著什麼樣的危機?

import java.util.*;
import java.io.*; public class BadExample1 {
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 (Exception e) {
e.printStackTrace ();
}
}
}

結果是:

java BadExample1
java.lang.IllegalThreadStateException: process has not exited
at java.lang.Win32Process.exitValue(Native Method)
at BadExample1.main(BadExample1.java:13)

奇怪,這麼簡單的程式碼,不過就是請exec執行一行javac,怎麼就跳出Exception了呢?讓我們來分析一下,首先這段程式用getRuntime()這個static method取得Runtime物件,然後呼叫exec去執行"javac"外部可執行檔,最後取得回傳值並把它輸出在標準輸出。問題來了,exec會create一個新的process來執行外部可執行檔,如果呼叫proc.exitValue()的時候javac還沒執行完畢,JVM就會丟出IllegalThreadStateException (記得嗎?在多工的作業系統裡,每個process幾乎可以被看成是同時運行的,既然如此沒有人可以保證process執行完成的先後順序。) ,有方法可以等外部process執行完畢嗎?YES,Process這個class提供了一個waitFor() method正好可以用來解決這個問題,而且waitFor()還會回傳等同於
exitValue()回傳值,所以程式再度改寫如下:

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

Sorry, Sorry, Sorry...讓我想起當紅的SorrySorry舞,很抱歉,這次程式直接當在那裡給你看,奇怪?為什麼會當,原來JDK裡面有一段話是這麼說的:

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.

好吧,所以以這個例子而言,應該要把stderr跟stdout裡面的buffer趕快讀走,再度改寫程式如下:

import java.util.*;
import java.io.*; public class OkExample1 {
public static void main (String args[]) {
try {
Runtime rt = Runtime.getRuntime ();
Process proc = rt.exec ("javac");
String line = null; InputStream stderr = proc.getErrorStream ();
InputStreamReader esr = new InputStreamReader (stderr);
BufferedReader ebr = new BufferedReader (esr);
System.out.println ("<error>");
while ( (line = ebr.readLine ()) != null)
System.out.println(line);
System.out.println ("</error>"); InputStream stdout = proc.getInputStream ();
InputStreamReader osr = new InputStreamReader (stdout);
BufferedReader obr = new BufferedReader (osr);
System.out.println ("<output>");
while ( (line = obr.readLine ()) != null)
System.out.println(line);
System.out.println ("</output>"); int exitVal = proc.waitFor ();
System.out.println ("Process exitValue: " + exitVal);
} catch (Exception e) {
e.printStackTrace();
}
}
}

輸出結果:

java OkExample1
<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

雖然程式醜了點,但至少結果是還OK的,更好的作法是create兩個thread分別把stdout與stderr的資料讀出來。不過這裡有一點需要注意,得到stdout的方法是呼叫getInputStream()名稱上比較容易搞混。

另一個常犯的錯誤是把console指令或shell指令當作是可執行檔,例如win32上的dir和copy:

import java.util.*;
import java.io.*; public class BadExample3 {
public static void main (String args[]) {
try {
Runtime rt = Runtime.getRuntime ();
Process proc = rt.exec ("dir"); InputStream stderr = proc.getErrorStream ();
InputStreamReader esr = new InputStreamReader (stderr);
BufferedReader ebr = new BufferedReader (esr);
System.out.println ("<error>");
while ( (line = ebr.readLine ()) != null)
System.out.println(line);
System.out.println ("</error>"); InputStream stdout = proc.getInputStream ();
InputStreamReader osr = new InputStreamReader (stdout);
BufferedReader obr = new BufferedReader (osr);
System.out.println ("<output>");
while ( (line = obr.readLine ()) != null)
System.out.println(line);
System.out.println ("</output>"); int exitVal = proc.waitFor ();
System.out.println ("Process exitValue: " + exitVal);
} catch (Exception e) {
e.printStackTrace();
}
}
}

輸出結果:

java BadExample3
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)

error=2表示找不到這個檔案,也就是系統並不存在dir.exe,哪是因為dir只是window command interpreter(command.com或cmd.exe)的其中一個指令。所以我們把上面的例子再改寫:

import java.util.*;
import java.io.*; class StreamConsumer extends Thread {
InputStream is;
String type; StreamConsumer (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 GoodExample1 {
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?
StreamConsumer errorConsumer = new
StreamConsumer (proc.getErrorStream(), "error"); // any output?
StreamConsumer outputConsumer = new
StreamConsumer (proc.getInputStream(), "output"); // kick them off
errorConsumer.start ();
outputCosumer.start (); // any error???
int exitVal = proc.waitFor ();
System.out.println ("ExitValue: " + exitVal);
} catch (Exception e) {
e.printStackTrace ();
}
}
}

輸出結果:

java GoodExample1 "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
... (省略)
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

還有一個常犯的錯誤是認為所有console或shell上可以執行的指令以為都可以透過exec()來達成,例如redirect >,請看:

import java.util.*;
import java.io.*; public class BadExample4 {
public static void main (String args[]) {
try {
Runtime rt = Runtime.getRuntime ();
Process proc = rt.exec ("echo 'Hello World' > test.txt");
// any error message?
StreamGobbler errorConsumer = new
StreamConsumer (proc.getErrorStream (), "error"); // any output?
StreamGobbler outputConsumer = new
StreamConsumer (proc.getInputStream (), "output"); // kick them off
errorConsumer.start();
outputConsumer.start(); // any error???
int exitVal = proc.waitFor ();
System.out.println ("ExitValue: " + exitVal);
} catch (Exception e) {
e.printStackTrace ();
}
}
}

輸出結果:

java BadExample4
OUTPUT>'Hello World' > test.txt
ExitValue: 0

如果這段command可以成功執行,那麼理論上test.txt應該會有一行'Hello World',但是事實上test.txt並不存在,也就是說redirect無法正確的被執行。

解決的辦法是:

  1. 自己開檔案把字串寫到test.txt裡面去。
  2. 建立一個.bat(win32)或.sh(linux)然後去執行。
 
參考資料:
 

如何正確的使用 Runtime.exec()的更多相关文章

  1. Runtime.exec() sucks!!!!

    自己项目中使用到了 Runtime rt = Runtime.getRuntime(); Process p = rt.exec("query session");p.waitFo ...

  2. Java魔法堂:找外援的利器——Runtime.exec详解

    一.前言 Java虽然五脏俱全但总有软肋,譬如获取CPU等硬件信息,当然我们可以通过JNI调用C/C++来获取,但对于对C/C++和Windows API不熟的码农是一系列复杂的学习和踩坑过程.那能不 ...

  3. Java运行系统命令并获取值(Process java.lang.Runtime.exec(String[] cmdarray, String[] envp, File dir)

    package test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; ...

  4. 权限执行[Android开发常见问题-4] RunTime.exec()如何以root权限执行多条指令?

    每日一贴,今天的内容关键字为权限执行 RunTime.exec()这个接口可以说是给我们开发者供给了一个很好的直观操纵底层操纵系统的机遇,但是这个接口的使用还有很多需要注意的问题.由于要完全的分析这个 ...

  5. Runtime.exec()

    关于RunTime类的介绍: /** * Every Java application has a single instance of class * <code>Runtime< ...

  6. Java Runtime.exec()的使用

    Sun的doc里其实说明还有其他的用法: exec(String[] cmdarray, String[] envp, File dir) Executes the specified command ...

  7. 执行Runtime.exec()需要注意的陷阱

    作为Java语言的一部分.java.lang包被隐藏的导入到每一个Java程序.这个包的表面陷阱,经常影响到大多数程序员.这个月,我将讨论运行时exec()方法时的潜伏陷阱. 陷阱4:当运行exec( ...

  8. [转]Java中Runtime.exec的一些事

    0 预备知识 1 不正确的调用exitValue 2不正确的调用waitFor 3 一种可接受的调用方式 4 调用认为是可执行程序的时候容易发生的错误 5 window执行的良好示例 6 不良好的重定 ...

  9. WPF 正確理解ContentPresenter

    我們先由下圖來看類層次,可知ContentControl繼承Control,ContentPresenter繼承FrameworkElement(Control也繼承FrameworkElement) ...

随机推荐

  1. [EZOJ1007] 神奇的三角形

    Description 求 \(\sum\limits_{i=0}^{n-1}\sum\limits_{j=0}^{i}C(i,j)\times (j+1)^m\operatorname{mod}99 ...

  2. 翻译:CREATE PROCEDURE语句(已提交到MariaDB官方手册)

    本文为mariadb官方手册:CREATE PROCEDURE的译文. 原文:https://mariadb.com/kb/en/create-procedure/我提交到MariaDB官方手册的译文 ...

  3. 常用的NoSQL数据库类型简述

    一.文档存储类型(Document Stores) 文档存储,也称为面向文档的数据库系统,其主要特点在于它们的无模式的数据组织. 特点: 1.记录数据不需要具有统一的结构,即不同的记录可以具有不同的列 ...

  4. HAProxy(三):Keeplived+HAProxy搭建高可用负载均衡动静分离架构基础配置示例

    一.安装环境 1.软件版本 HAProxy:1.5.18 Keepalived:1.3.5 Nginx:1.12.2 PHP:7.2 系统版本:CentOS 7.4 2.IP分配与架构图 3.安装软件 ...

  5. 【手记】sql报“聚合或其他set操作消除了null值”处理

    这个警告在常规场景中没什么影响,但如果是用excel跑SQL,它会因为该警告阻止你的后续操作~事实上excel执行sql限制多多,需要更多的奇技淫巧,之前我就写过一篇.言归正传,要解决这个警告,一种当 ...

  6. Ocelot中文文档-Configuration

    配置 一个关于Ocelot配置例子在这里.配置有两个部分.一个数组类型的ReRoutes和一个全局配置.ReRoutes是个对象,告诉Ocelot怎么去处理一个上游请求.全局配置有点繁琐(is a h ...

  7. JAVA中的集合容器操作类

    目录 JAVA中的集合容器操作类 List集合 ArrayList的操作方法说明 LinkedList Stack Set Map Queue 总结 JAVA中的集合容器操作类 Java容器类库总共分 ...

  8. 将第三方包安装到maven本地仓库

    今天在做jasper report生成pdf文档的时候,需要引入亚洲字体jar包.maven仓库是有这个jar包,但是在项目pom文件始终不能下载.无奈只有将jar包安装到maven本地仓库. 1 将 ...

  9. 了解java虚拟机—在TALB上分配对象(10)

    由于对象一般会分配在堆上,而堆是全局共享的.因此在同一时间,可能有多个线程在堆上申请空间.每次对象分内都必须要进行同步,因此TLAB这种线程专属的区域来避免多线程冲突.TLAB本身占用了eden区的空 ...

  10. Contest2073 - 湖南多校对抗赛(2015.04.06)

    Contest2073 - 湖南多校对抗赛(2015.04.06) Problem A: (More) Multiplication Time Limit: 1 Sec  Memory Limit:  ...