從事java開發已經有了大半年的時間,一直沒有在java裏面調用shell腳本,之前在c語言裏面用過system、popen或者execl等函數來調用過shell腳本,感受很是好用,其實java裏面也能夠調用shell腳本,用兩種調用shell腳本的方法。 html
ProcessBuilder pb=new ProcessBuilder(cmd); pb.start();
代碼以下:java
import java.io.File; import java.io.IOException; public class JavaShellUtil1 { public static void main(String[] args) { ProcessBuilder builder=new ProcessBuilder("/bin/sh","-c","/home/songjy.sh a b >/home/songjy.log 2>&1"); builder.directory(new File("/home/")); int runningStatus = 0; String s = null; try { Process pro=builder.start(); System.out.println("the shell script running"); try { runningStatus=pro.waitFor(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(runningStatus!=0){ System.out.println("腳本執行失敗"); }else{ System.out.println("腳本執行成功"); } System.out.println("11111111111"); } }
可是這個兩種方法都有個問題,執行諸如shell
:ps -ef | grep -v grep或者 /home/songjy.sh a b >/home/songjy.log 2>&1"數組
帶有管道或重定向的命令就會出錯。咱們都知道使用以上兩種方法執行命令時,若是帶有參數就要把命令分割成數組或者List傳入,否則會被當成一個總體執行(會出錯,好比執行"ps -e")。對於|,<,>號來講,這樣作也不行。對於Linux系統,解決方法就是把整個命令都當成sh的參數傳入,用sh來執行命令。app
ProcessBuilder builder=new ProcessBuilder("/bin/sh","-c","/home/songjy.sh a b >/home/songjy.log 2>&1");
Windows下把sh換成cmd.exe就好了。函數
這兒其實還作了另外的一個處理,就是將標準輸入和標準出錯打印重定向到日誌裏面,就要就不用用pid.getInputStream 和pid.getErrorStream 去將其讀出來了(防止會一直阻塞,java一直等待shell的返回
ui
這個問題估計更加常常遇到。 緣由是, shell腳本中有echo或者print輸出, 致使緩衝區被用完了! 爲了不這種狀況, 必定要把緩衝區讀一下, 好處就是,能夠對shell的具體運行狀態進行log出來)this
Runtime.getRuntime().exec(cmd)
import java.io.BufferedReader; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; //http://kongcodecenter.iteye.com/blog/1231177 //參考http://siye1982.iteye.com/blog/592405 //參考http://blog.csdn.net/christophe2008/article/details/6046456 public class JavaShellUtil { // 基本路徑 private static final String basePath = "/home/"; // 記錄Shell執行情況的日誌文件的位置(絕對路徑) private static final String executeShellLogFile = basePath + "executeShell.log"; // 發送文件到Kondor系統的Shell的文件名(絕對路徑) private static final String sendKondorShellName = basePath + "songjy.sh"; public int executeShell(String shellCommand) throws IOException { System.out.println("shellCommand:"+shellCommand); int success = 0; StringBuffer stringBuffer = new StringBuffer(); BufferedReader bufferedReader = null; BufferedReader stdError=null; // 格式化日期時間,記錄日誌時使用 DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:SS "); try { stringBuffer.append(dateFormat.format(new Date())) .append("準備執行Shell命令 ").append(shellCommand) .append(" \r\n"); Process pid = null; String[] cmd = { "/bin/sh", "-c", shellCommand }; // 執行Shell命令 pid = Runtime.getRuntime().exec(cmd); if (pid != null) { stringBuffer.append("進程號:").append(pid.toString()) .append("\r\n"); // bufferedReader用於讀取Shell的輸出內容 bufferedReader = new BufferedReader(new InputStreamReader(pid.getInputStream())); //讀到標準出錯的信息 stdError = new BufferedReader(new InputStreamReader(pid.getErrorStream())); //這個是或得腳本執行的返回值 int status=pid.waitFor(); //若是腳本執行的返回值不是0,則表示腳本執行失敗,不然(值爲0)腳本執行成功。 if(status!=0) { stringBuffer.append("shell腳本執行失敗!"); } else{ stringBuffer.append("shell腳本執行成功!"); } } else { stringBuffer.append("沒有pid\r\n"); } stringBuffer.append(dateFormat.format(new Date())).append( "Shell命令執行完畢\r\n執行結果爲:\r\n"); //將標準輸入流上面的內容寫到stringBuffer裏面 String line = null; // 讀取Shell的輸出內容,並添加到stringBuffer中 while (bufferedReader != null && (line = bufferedReader.readLine()) != null) { stringBuffer.append(line).append("\r\n"); } //將標準輸入流上面的內容寫到stringBuffer裏面 String line1 = null; while(stdError !=null &&(line1 = stdError.readLine()) != null){ stringBuffer.append(line1).append("\r\n"); } System.out.println("stringBuffer:"+stringBuffer); } catch (Exception ioe) { stringBuffer.append("執行Shell命令時發生異常:\r\n").append(ioe.getMessage()) .append("\r\n"); } finally { if (bufferedReader != null) { OutputStreamWriter outputStreamWriter = null; try { bufferedReader.close(); // 將Shell的執行狀況輸出到日誌文件中 OutputStream outputStream = new FileOutputStream(executeShellLogFile); outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8"); outputStreamWriter.write(stringBuffer.toString()); System.out.println("stringBuffer.toString():"+stringBuffer.toString()); } catch (Exception e) { e.printStackTrace(); } finally { outputStreamWriter.close(); } } success = 1; } return success; } public static void main(String[] args) { try { new JavaShellUtil().executeShell(sendKondorShellName); } catch (IOException e) { e.printStackTrace(); } } }
這個裏面就是用pid.getInputStream 和pid.getErrorStream 去讀緩衝區的。spa
在Java中,能夠經過兩種方式來建立進程,總共涉及到5個主要的類。.net
第一種方式是經過Runtime.exec()方法來建立一個進程,第二種方法是經過ProcessBuilder的start方法來建立進程。下面就來說一講這2種方式的區別和聯繫。
首先要講的是Process類,Process類是一個抽象類,在它裏面主要有幾個抽象的方法,這個能夠經過查看Process類的源代碼得知:
位於java.lang.Process路徑下:
public abstract class Process { abstract public OutputStream getOutputStream(); //獲取進程的輸出流 abstract public InputStream getInputStream(); //獲取進程的輸入流 abstract public InputStream getErrorStream(); //獲取進程的錯誤流 abstract public int waitFor() throws InterruptedException; //讓進程等待 abstract public int exitValue(); //獲取進程的退出標誌 abstract public void destroy(); //摧毀進程 }
1)經過ProcessBuilder建立進程
ProcessBuilder是一個final類,它有兩個構造器:
public final class ProcessBuilder { private List<String> command; private File directory; private Map<String,String> environment; private boolean redirectErrorStream; public ProcessBuilder(List<String> command) { if (command == null) throw new NullPointerException(); this.command = command; } public ProcessBuilder(String... command) { this.command = new ArrayList<String>(command.length); for (String arg : command) this.command.add(arg); } .... }
構造器中傳遞的是須要建立的進程的命令參數,第一個構造器是將命令參數放進List當中傳進去,第二構造器是以不定長字符串的形式傳進去。
那麼咱們接着往下看,前面提到是經過ProcessBuilder的start方法來建立一個新進程的,咱們看一下start方法中具體作了哪些事情。下面是start方法的具體實現源代碼:
public Process start() throws IOException { // Must convert to array first -- a malicious user-supplied // list might try to circumvent the security check. String[] cmdarray = command.toArray(new String[command.size()]); for (String arg : cmdarray) if (arg == null) throw new NullPointerException(); // Throws IndexOutOfBoundsException if command is empty String prog = cmdarray[0]; SecurityManager security = System.getSecurityManager(); if (security != null) security.checkExec(prog); String dir = directory == null ? null : directory.toString(); try { return ProcessImpl.start(cmdarray, environment, dir, redirectErrorStream); } catch (IOException e) { // It's much easier for us to create a high-quality error // message than the low-level C code which found the problem. throw new IOException( "Cannot run program \"" + prog + "\"" + (dir == null ? "" : " (in directory \"" + dir + "\")") + ": " + e.getMessage(), e); } }
該方法返回一個Process對象,該方法的前面部分至關因而根據命令參數以及設置的工做目錄進行一些參數設定,最重要的是try語句塊裏面的一句:
return ProcessImpl.start(cmdarray, environment, dir, redirectErrorStream);
說明真正建立進程的是這一句,注意調用的是ProcessImpl類的start方法,此處能夠知道start必然是一個靜態方法。那麼ProcessImpl又是什麼類呢?該類一樣位於java.lang.ProcessImpl路徑下,看一下該類的具體實現:
ProcessImpl也是一個final類,它繼承了Process類:
final class ProcessImpl extends Process { // System-dependent portion of ProcessBuilder.start() static Process start(String cmdarray[], java.util.Map<String,String> environment, String dir, boolean redirectErrorStream) throws IOException { String envblock = ProcessEnvironment.toEnvironmentBlock(environment); return new ProcessImpl(cmdarray, envblock, dir, redirectErrorStream); } .... }
這是ProcessImpl類的start方法的具體實現,而事實上start方法中是經過這句來建立一個ProcessImpl對象的:
return new ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);
而在ProcessImpl中對Process類中的幾個抽象方法進行了具體實現。
說明事實上經過ProcessBuilder的start方法建立的是一個ProcessImpl對象。
public class Test { public static void main(String[] args) throws IOException { ProcessBuilder pb = new ProcessBuilder("cmd","/c","ipconfig/all"); Process process = pb.start(); Scanner scanner = new Scanner(process.getInputStream()); while(scanner.hasNextLine()){ System.out.println(scanner.nextLine()); } scanner.close(); } }
第一步是最關鍵的,就是將命令字符串傳給ProcessBuilder的構造器,通常來講,是把字符串中的每一個獨立的命令做爲一個單獨的參數,不過也能夠按照順序放入List中傳進去。
至於其餘不少具體的用法不在此進行贅述,好比經過ProcessBuilder的environment方法和directory(File directory)設置進程的環境變量以及工做目錄等,感興趣的朋友能夠查看相關API文檔。
2)經過Runtime的exec方法來建立進程
首先仍是來看一下Runtime類和exec方法的具體實現,Runtime,顧名思義,即運行時,表示當前進程所在的虛擬機實例。
因爲任何進程只會運行於一個虛擬機實例當中,因此在Runtime中採用了單例模式,即只會產生一個虛擬機實例:
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} ... }
從這裏能夠看出,因爲Runtime類的構造器是private的,因此只有經過getRuntime去獲取Runtime的實例。接下來着重看一下exec方法 實現,在Runtime中有多個exec的不一樣重載實現,但真正最後執行的是這個版本的exec方法:
public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException { return new ProcessBuilder(cmdarray) .environment(envp) .directory(dir) .start(); }
能夠發現,事實上經過Runtime類的exec建立進程的話,最終仍是經過ProcessBuilder類的start方法來建立的。
下面看一個例子,看一下經過Runtime的exec如何建立進程,仍是前面的例子,調用cmd,獲取ip地址信息:
public class Test { public static void main(String[] args) throws IOException { String cmd = "cmd "+"/c "+"ipconfig/all"; Process process = Runtime.getRuntime().exec(cmd); Scanner scanner = new Scanner(process.getInputStream()); while(scanner.hasNextLine()){ System.out.println(scanner.nextLine()); } scanner.close(); } }
要注意的是,exec方法不支持不定長參數(ProcessBuilder是支持不定長參數的),因此必須先把命令參數拼接好再傳進去。
參考文章
http://www.cnblogs.com/dolphin0520/p/3913517.html
http://luckykapok918.blog.163.com/blog/static/205865043201210272168556/