轉自:https://www.cnblogs.com/mingforyou/p/3551199.html
Java Runtime.exec()的使用
Sun的doc里其實說明還有其他的用法:
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就是調用的程序的工作目錄,這句其實還是很有用的。
Windows下調用程序
Process proc =Runtime.getRuntime().exec("exefile");
Linux下調用程序就要改成下面的格式
Process proc =Runtime.getRuntime().exec("./exefile");
Windows下調用系統命令
String [] cmd={"cmd","/C","copy exe1 exe2"}; Process proc =Runtime.getRuntime().exec(cmd);
Linux下調用系統命令就要改成下面的格式
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下調用系統命令並彈出終端窗口就要改成下面的格式
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的。我說幾個自己覺得重要的地方。
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是否被執行。
現在開始看這篇文章,呵呵。
首先講的是Runtime.exec() 方法的所有重載。這里要注意的有一點,就是public Process exec(String [] cmdArray, String [] envp);這個方法中cmdArray是一個執行的命令和參數的字符串數組,數組的第一個元素是要執行的命令往后依次都是命令的參數,envp我個人感 覺應該和C中的execve中的環境變量是一樣的,envp中使用的是name=value的方式。
<!---->1、 <!---->一個很糟糕的調用程序,代碼如下,這個程序用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("<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()調用時出現錯誤。
<!---->1、 <!---->在一個外部進程執行完之前你不能得到他的退出狀態
<!---->2、 <!---->在你的外部程序開始執行的時候你必須馬上控制輸入、輸出、出錯這些流。
<!---->3、 <!---->你必須用Runtime.exec()去執行程序
<!---->4、 <!---->你不能象命令行一樣使用Runtime.exec()。