Java Runtime.exec()的使用

Sun的doc裏其實說明還有其餘的用法:html

exec(String[] cmdarray, String[] envp, File dir)

Executes the specified command and arguments in a separate process with the specified environment and working directory.

那個dir就是調用的程序的工做目錄,這句其實仍是頗有用的。java

Windows下調用程序程序員

Process proc =Runtime.getRuntime().exec("exefile");

Linux下調用程序就要改爲下面的格式web

Process proc =Runtime.getRuntime().exec("./exefile");

Windows下調用系統命令shell

String [] cmd={"cmd","/C","copy exe1 exe2"}; 
Process proc =Runtime.getRuntime().exec(cmd);

Linux下調用系統命令就要改爲下面的格式bootstrap

String [] cmd={"/bin/sh","-c","ln -s exe1 exe2"}; 
Process proc =Runtime.getRuntime().exec(cmd);

Windows下調用系統命令並彈出命令行窗口小程序

String [] cmd={"cmd","/C","start copy exe1 exe2"}; 
Process proc =Runtime.getRuntime().exec(cmd);

Linux下調用系統命令並彈出終端窗口就要改爲下面的格式windows

String [] cmd={"/bin/sh","-c","xterm -e ln -s exe1 exe2"};
Process proc =Runtime.getRuntime().exec(cmd);

還有要設置調用程序的工做目錄就要數組

Process proc =Runtime.getRuntime().exec("exeflie",null, new File("workpath"));

固然最好的執行系統命令的方法就是寫個bat文件或是shell腳本。而後調用,那樣修改和實現就簡點多了。安全

還有在在Java程序中截獲控制檯輸出[轉]這篇文章中有詳細的如何在JTextArea中顯示攔截的控制檯輸出。

 

JAVA如今執行外部命令,主要的方式,仍是經過調用因此平臺的SHELL去完成,WINDOWS下面就用CMD,LINUX或者是UNIX下面就用SHELL,下面演示一個對BAT文件的調用,並把結果回顯到控制檯上,其它的應用程序類。  說明:  一個調用SHELL執行外部  取得外部程序的輸出流,採用適當的READER讀回來,並顯示出來就OK了  下面是源程序:

import java.io.BufferedReader;    
import java.io.IOException;    
import java.io.InputStream;    
import java.io.InputStreamReader;    
   
public class JavaExeBat    
{    
    public static void main(String[] args)    
     {    
         Process p;    
        //test.bat中的命令是ipconfig/all    
         String cmd="c:\\test\\test.bat";    
            
        try    
         {    
            //執行命令    
             p = Runtime.getRuntime().exec(cmd);    
            //取得命令結果的輸出流    
             InputStream fis=p.getInputStream();    
            //用一個讀輸出流類去讀    
             InputStreamReader isr=new InputStreamReader(fis);    
            //用緩衝器讀行    
             BufferedReader br=new BufferedReader(isr);    
             String line=null;    
            //直到讀完爲止    
            while((line=br.readLine())!=null)    
             {    
                 System.out.println(line);    
             }    
         }    
        catch (IOException e)    
         {    
             e.printStackTrace();    
         }    
     }    
}   

 執行結果以下: 

Windows IP Configuration 

Host Name . . . . . . . . . . . . : Mickey 

Primary Dns Suffix . . . . . . . : 

Node Type . . . . . . . . . . . . : Unknown 

IP Routing Enabled. . . . . . . . : No 

WINS Proxy Enabled. . . . . . . . : No 

DNS Suffix Search List. . . . . . : domain 
Ethernet adapter 本地鏈接: 

Connection-specific DNS Suffix . : domain 

Description . . . . . . . . . . . : Broadcom NetXtreme Gigabit Ethernet 

 

那就首先說點Runtime類吧,他是一個與JVM運行時環境有關的類,這個類是Singleton的。我說幾個本身以爲重要的地方。

一、Runtime.getRuntime()能夠取得當前JVM的運行時環境,這也是在Java中惟一一個獲得運行時環境的方法。

二、Runtime上其餘大部分的方法都是實例方法,也就是說每次進行運行時調用時都要用到getRuntime方法。

三、 Runtime中的exit方法是退出當前JVM的方法,估計也是惟一的一個吧,由於我看到System類中的exit實際上也是經過調用 Runtime.exit()來退出JVM的,這裏說明一下Java對Runtime返回值的通常規則(後邊也提到了),0表明正常退出,非0表明異常中 止,這只是Java的規則,在各個操做系統中總會發生一些小的混淆。

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

五、 說到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是否被執行。

如今開始看這篇文章,呵呵。

首先講的是Runtime.exec() 方法的全部重載。這裏要注意的有一點,就是public Process exec(String [] cmdArray, String [] envp);這個方法中cmdArray是一個執行的命令和參數的字符串數組,數組的第一個元素是要執行的命令日後依次都是命令的參數,envp我我的感 覺應該和C中的execve中的環境變量是同樣的,envp中使用的是name=value的方式。

<!---->一、 <!---->一個很糟糕的調用程序,代碼以下,這個程序用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來取得外部命令的返回狀態,並在外部命令返回時做出相應的處理。

二、對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("<error></error>");
            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 <error></error> Usage: javac <options></options> <source files=""></source>

where <options></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
Specify where to find user class files -sourcepath Specify where to find input source files -bootclasspath
Override location of bootstrap class files -extdirs <dirs></dirs>Override location of installed extensions -d <directory></directory>
Specify where to place generated class files -encoding <encoding></encoding>
Specify character encoding used by source files -target <release></release>
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("<output></output>");
            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.<init></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)

 

說實在的,這個錯誤還真是讓我摸不着頭腦,我以爲在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 <cmd></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
        }                                        
    }
}

 

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 S
uperFrame.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");

 

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[]) {
        try {  
            FileOutputStream fos = new FileOutputStream(args[0]); 
            Runtime rt = Runtime.getRuntime(); 
            Process proc = rt.exec("java jecho 'Hello World'"); // any
        }
        if (args.length < 1) {
            System.out.println("USAGE java GoodWinRedirect <outputfile></outputfile>"); 
            System.exit(1);
        }
    }
}

 

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[]) { 
        try { 
            String cmd = args[0]; 
            Runtime rt = Runtime.getRuntime(); 
            Process proc = rt.exec(cmd);
            if (args.length < 1) { 
                System.out.println("USAGE: java TestExec "+cmd); 
                System.exit(1); 
            }
        }
    }
}



// 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.<init></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在windows中是說這不是一個win32程序,這說明路徑中找不到這個網頁的關聯程序,下面做者決定用一個絕對路徑來試一下。

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

 

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

最後,做者總結了幾條規則,防止咱們在進行Runtime.exec()調用時出現錯誤。

<!---->一、 <!---->在一個外部進程執行完以前你不能獲得他的退出狀態

<!---->二、 <!---->在你的外部程序開始執行的時候你必須立刻控制輸入、輸出、出錯這些流。

<!---->三、 <!---->你必須用Runtime.exec()去執行程序

<!---->四、 <!---->你不能象命令行同樣使用Runtime.exec()。

相關文章
相關標籤/搜索