java runtime.exec() 的編寫

gagaghost發表於2007-05-13

那就首先說點Runtime類吧,他是一個與JVM執行時環境有關的類,這個類是Singleton的。我說幾個自己覺得重要的地方。

1、Runtime.getRuntime()可以取得當前JVM的執行時環境,這也是在Java中唯一一個得到執行時環境的方法。

2、Runtime上其他大部分的方法都是例項方法,也就是說每次進行執行時呼叫時都要用到getRuntime方法。

3、Runtime中的exit方法是退出當前JVM的方法,估計也是唯一的一個吧,因為我看到System類中的exit實際上也是透過呼叫Runtime.exit()來退出JVM的,這裡說明一下Java對Runtime返回值的一般規則(後邊也提到了),0代表正常退出,非0代表異常中止,這只是Java的規則,在各個作業系統中總會發生一些小的混淆。


4、Runtime.addShutdownHook()方法可以註冊一個hook在JVM執行shutdown的過程中,方法的引數只要是一個初始化過但是沒有執行的Thread例項就可以。(注意,Java中的Thread都是執行過了就不值錢的哦)

5、說到addShutdownHook這個方法就要說一下JVM執行環境是在什麼情況下shutdown或者abort的。文件上是這樣寫的,當最後一個非精靈程式退出或者收到了一個使用者中斷訊號、使用者登出、系統shutdown、Runtime的exit方法被呼叫時JVM會啟動shutdown的過程,在這個過程開始後,他會並行啟動所有登記的shutdown hook(注意是並行啟動,這就需要執行緒安全和防止死鎖)。當shutdown過程啟動後,只有透過呼叫halt方法才能中止shutdown的過程並退出JVM。

那什麼時候JVM會abort退出那?首先說明一下,abort退出時JVM就是停止執行但並不一定進行shutdown。這隻有JVM在遇到SIGKILL訊號或者windows中止程式的訊號、本地方法發生類似於訪問非法地址一類的內部錯誤時會出現。這種情況下並不能保證shutdown hook是否被執行。

[@more@]首先講的是Runtime.exec()方法的所有過載。這裡要注意的有一點,就是public Process exec(String [] cmdArray, String [] envp);這個方法中cmdArray是一個執行的命令和引數的字串陣列,陣列的第一個元素是要執行的命令往後依次都是命令的引數,envp我個人感覺應該和C中的execve中的環境變數是一樣的,envp中使用的是name=value的方式。


<!--[if !supportLists]--&gt1、 <!--[endif]--&gt一個很糟糕的呼叫程式,程式碼如下,這個程式用exec呼叫了一個外部命令之後馬上使用exitValue就對其返回值進行檢查,讓我們看看會出現什麼問題。


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:classescomjavaworldjpitfallsarticle2>java BadExecJavac
java.lang.IllegalThreadStateException: process has not exited
at java.lang.Win32Process.exitValue(Native Method)
at BadExecJavac.main(BadExecJavac.java:13)


這裡看原文就可以瞭解,這裡主要的問題就是錯誤的呼叫了exitValue來取得外部命令的返回值(呵呵,這個錯誤我也曾經犯過),因為exitValue這個方法是不阻塞的,程式在呼叫這個方法時外部命令並沒有返回所以造成了異常的出現,這裡是由另外的方法來等待外部命令執行完畢的,就是waitFor方法,這個方法會一直阻塞直到外部命令執行結束,然後返回外部命令執行的結果,作者在這裡一頓批評設計者的思路有問題,呵呵,反正我是無所謂阿,能用就可以拉。但是作者在這裡有一個說明,就是exitValue也是有好多用途的。因為當你在一個Process上呼叫waitFor方法時,當前執行緒是阻塞的,如果外部命令無法執行結束,那麼你的執行緒就會一直阻塞下去,這種意外會影響我們程式的執行。所以在我們不能判斷外部命令什麼時候執行完畢而我們的程式還需要繼續執行的情況下,我們就應該迴圈的使用exitValue來取得外部命令的返回狀態,並在外部命令返回時作出相應的處理。


2、對exitValue處改進了的程式

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();
}
}
}

不幸的是,這個程式也無法執行完成,它沒有輸出但卻一直懸在那裡,這是為什麼那?


JDK文件中對此有如此的解釋:因為本地的系統對標準輸入和輸出所提供的緩衝池有效,所以錯誤的對標準輸出快速的寫入和從標準輸入快速的讀入都有可能造成子程式的鎖,甚至死鎖。


文件引述完了,作者又開始批評了,他說JDK僅僅說明為什麼問題會發生,卻並沒有說明這個問題怎麼解決,這的確是個問題哈。緊接著作者說出自己的做法,就是在執行完外部命令後我們要控制好Process的所有輸入和輸出(視情況而定),在這個例子裡邊因為呼叫的是Javac,而他在沒有引數的情況下會將提示資訊輸出到標準出錯,所以在下面的程式中我們要對此進行處理。


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("");
while ( (line = br.readLine()) != null)
System.out.println(line);
System.out.println("
");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}


程式的執行結果為

E:classescomjavaworldjpitfallsarticle2>java MediocreExecJavac

Usage: javac <source>

where 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 Specify where to find user class files
-sourcepath Specify where to find input source files
-bootclasspath Override location of bootstrap class files
-extdirs Override location of installed extensions
-d Specify where to place generated class files
-encoding Specify character encoding used by source files
-target Generate class files for specific VM version

Process exitValue: 2


哎,不管怎麼說還是出來了結果,作者作了一下總結,就是說,為了處理好外部命令大量輸出的情況,你要確保你的程式處理好外部命令所需要的輸入或者輸出。


下一個題目,當我們呼叫一個我們認為是可執行程式的時候容易發生的錯誤(今天晚上我剛剛犯這個錯誤,沒事做這個練習時候發生的)

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("");
while ( (line = br.readLine()) != null)
System.out.println(line);
System.out.println("
");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}

A run of BadExecWinDir produces:


E:classescomjavaworldjpitfallsarticle2>java BadExecWinDir
java.io.IOException: CreateProcess: dir error=2
at java.lang.Win32Process.create(Native Method)
at java.lang.Win32Process.(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)


說實在的,這個錯誤還真是讓我摸不著頭腦,我覺得在windows中返回2應該是沒有找到這個檔案的緣故,可能windows 2000中只有cmd命令,dir命令不是當前環境變數能夠解釋的吧。我也不知道了,慢慢往下看吧。

嘿,果然和作者想的一樣,就是因為dir命令是由windows中的直譯器解釋的,直接執行dir時無法找到dir.exe這個命令,所以會出現檔案未找到這個2的錯誤。如果我們要執行這樣的命令,就要先根據作業系統的不同執行不同的解釋程式command.com 或者cmd.exe。

作者對上邊的程式進行了修改

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 ");
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:classescomjavaworldjpitfallsarticle2>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:classescomjavaworldjpitfallsarticle2
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

這裡作者教了一個windows中很有用的方法,呵呵,至少我是不知道的,就是cmd.exe /C +一個windows中註冊了字尾的文件名,windows會自動地呼叫相關的程式來開啟這個文件,我試了一下,的確很好用,但是好像檔案路徑中有空格的話就有點問題,我加上引號也無法解決。

這裡作者強調了一下,不要假設你執行的程式是可執行的程式,要清楚自己的程式是單獨可執行的還是被解釋的,本章的結束作者會介紹一個命令列工具來幫助我們分析。

這裡還有一點,就是得到process的輸出的方式是getInputStream,這是因為我們要從Java 程式的角度來看,外部程式的輸出對於Java來說就是輸入,反之亦然。


最後的一個漏洞的地方就是錯誤的認為exec方法會接受所有你在命令列或者Shell中輸入並接受的字串。這些錯誤主要出現在命令作為引數的情況下,程式設計師錯誤的將所有命令列中可以輸入的引數命令加入到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();
}
}
}

Running BadWinRedirect produces:


E:classescomjavaworldjpitfallsarticle2>java BadWinRedirect
OUTPUT>'Hello World' > test.txt
ExitValue: 0

程式設計師的本意是將Hello World這個輸入重訂向到一個文字檔案中,但是這個檔案並沒有生成,jecho僅僅是將命令列中的引數輸出到標準輸出中,使用者覺得可以像dos中重定向一樣將輸出重定向到一個檔案中,但這並不能實現,使用者錯誤的將exec認為是一個shell直譯器,但它並不是,如果你想將一個程式的輸出重定向到其他的程式中,你必須用程式來實現他。可用java.io中的包。


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 ");
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:classescomjavaworldjpitfallsarticle2>java GoodWinRedirect test.txt
OUTPUT>'Hello World'
ExitValue: 0

這裡就不多說了,看看就明白,緊接著作者給出了一個監測命令的小程式

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:classescomjavaworldjpitfallsarticle2>java TestExec "e:javadocsindex.html"
java.io.IOException: CreateProcess: e:javadocsindex.html error=193
at java.lang.Win32Process.create(Native Method)
at java.lang.Win32Process.(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在windows中是說這不是一個win32程式,這說明路徑中找不到這個網頁的關聯程式,下面作者決定用一個絕對路徑來試一下。

E:classescomjavaworldjpitfallsarticle2>java TestExec
"e:program filesnetscapeprogramnetscape.exe e:javadocsindex.html"
ExitValue: 0


好用了,這個我也試了一下,用的是IE。


最後,作者總結了幾條規則,防止我們在進行Runtime.exec()呼叫時出現錯誤。


<!--[if !supportLists]--&gt1、 <!--[endif]--&gt在一個外部程式執行完之前你不能得到他的退出狀態

<!--[if !supportLists]--&gt2、 <!--[endif]--&gt在你的外部程式開始執行的時候你必須馬上控制輸入、輸出、出錯這些流。

<!--[if !supportLists]--&gt3、 <!--[endif]--&gt你必須用Runtime.exec()去執行程式

<!--[if !supportLists]--&gt4、 <!--[endif]--&gt你不能象命令列一樣使用Runtime.exec()。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/5859/viewspace-914841/,如需轉載,請註明出處,否則將追究法律責任。

相關文章