解決使用ProcessBuilder踩到的坑及註意事項
使用ProcessBuilder踩到的坑
最近使用ProcessBuilder執行命令,命令內容正確,但始終報錯命令實行失敗,是因為不熟悉ProcessBuilder用法踩到瞭坑,記錄一下。
先看一下我模擬出來的錯誤
要執行的命令:cp -rf /tmp/monkey/a.log /home/monkey/ 簡單的cp命令拷貝一個文件,卻報錯說文件不存在。確認過文件確實存在該目錄下。
查看jdk 中,我使用的ProcessBuilder(***) 源碼實現如下,並不是一個單獨的字符串String形式,而是支持多個字符串,同時還有List集合方式。
於是想到會不會是ProcessBuilderbuilder不支持包含空格的命令。
動手寫瞭下面的代碼進行測試
public class ProcessBuilderDemo { /** * 測試processBuilder執行cp命令 * cp /tmp/monkey/a.log /home/monkey/ * 源路徑 args[1]: /tmp/monkey/a.log * 目標路徑 args[2]: /home/monkey/ * 方法名 args[3] * @param args */ public static void main(String[] args) { String src = args[0]; String tag = args[1]; String method = args.length == 3 ? args[2] : null; if (method != null && method.equals("string")) { cmdIsString(src, tag); } else { cmdIsListOrArray(src, tag); } } /** * 執行命令,命令用拼接成一個字符串形式(會包含空格) * @param src 源路徑 * @param tag 目標路徑 */ private static void cmdIsString(String src, String tag) { String cmd = "cp"; cmd = cmd + " -rf" + " " + src + " " + tag; System.out.println("command is: " + cmd); ProcessBuilder builder = new ProcessBuilder(cmd); try { Process process = builder.start(); process.waitFor(); } catch (Exception e) { e.printStackTrace(); System.out.println(e.toString()); } } /** * 執行命令,命令各個部分拼接成一個數組或者ArrayList集合 * 該方法采用數組實現 * @param src 源路徑 * @param tag 目標路徑 */ private static void cmdIsListOrArray(String src, String tag) { String cmd = "cp"; // 命令的各個部分組成一個字符串數組,用該數組創建ProcessBuilder對象 String[] cmds = new String[] {cmd, "-rf", src, tag}; ProcessBuilder builder = new ProcessBuilder(cmds); try { Process process = builder.start(); process.waitFor(); } catch (Exception e) { e.printStackTrace(); System.out.println(e.toString()); } } }
果然如我所猜想的一樣:包含有空格的命令執行會報錯。
以下是cmdIsListOrArray方法,將命令的內容組成字符串的形式執行的結果,而文章第一張圖則是直接當做一條完整命令的執行結果。
至於為什麼不能好有空格暫時未做深入瞭解,有帶佬可以釋疑嗎?難道一條完整的命令當做一個字符串它不香嘛?
while(true) { 伸手黨; }
使用ProcessBuilder執行本地程序的註意事項
錯誤代碼
public static void main(String[] args) throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder("java","-version"); Process process = processBuilder.start(); InputStream inputStream = process.getInputStream(); xxx(inputStream); } private static void xxx(InputStream inputStream) throws IOException { BufferedReader input = new BufferedReader(new InputStreamReader(inputStream)); String ss=null; while ((ss=input.readLine())!=null){ System.out.println(ss); } }
使用ProcessBuilder類帶參執行命令容易出現的兩個坑
1、執行後沒有任何反映
原因為通過ProcessBuilder運行的參數還沒有執行完畢程序就退出瞭。
通過if(process.isAlive()){process.waitFor();}可以規避此問題,但是需要註意waitFor時程序時阻塞的,如果是持續運行的web項目可以通過開啟子線程來執行ProcessBuilder
2、執行後沒有任何輸出
最惡心的地方,除瞭getInputStream外還有一個getErrorStream也可以獲取數據,而且一般執行的程序數據都會輸出在getErrorStream中,所以getInputStream無法獲取到數據
處理後的代碼
public static void main(String[] args) throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder("java","-version"); processBuilder.redirectErrorStream(true);//將錯誤流中的數據合並到輸入流 Process process = processBuilder.start(); if(process.isAlive()){ process.waitFor(); } // InputStream errorStream = process.getErrorStream(); InputStream inputStream = process.getInputStream(); // xxx(errorStream); xxx(inputStream); } private static void xxx(InputStream inputStream) throws IOException { BufferedReader input = new BufferedReader(new InputStreamReader(inputStream)); String ss=null; while ((ss=input.readLine())!=null){ System.out.println(ss); } }
後續發現新的問題,當某個軟件會持續向流中寫數據,這時流中數據沒有被讀取完畢(流中存在數據【測試發現流中存在數據並不是一定會阻塞】),會導致waitFor一直陷入阻塞
上述問題處理後的代碼(正確使用ProcessBuilder的代碼)
public static void main(String[] args) throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder("java","-version"); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); // 通過標準輸入流來拿到正常和錯誤的信息 InputStream inputStream = process.getInputStream(); BufferedReader input = new BufferedReader(new InputStreamReader(inputStream)); String ss=null; while ((ss=input.readLine())!=null){ System.out.println(ss); } process.waitFor(); }
復現錯誤:
1、某個軟件持續向流中寫數據時,如果流中數據未被讀取完畢waitFor一直陷入等待
public static void main(String[] args) throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder(); List<String> meta = new ArrayList<String>(); meta.add("ffmpeg"); meta.add("-i"); meta.add("C:/Users/Lenovo/Desktop/20200801134820261.mp3"); meta.add("-af"); meta.add("silencedetect=n=-1dB:d=0.5"); meta.add("-f"); meta.add("null"); meta.add("-"); processBuilder.command(meta); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); InputStream inputStream = process.getInputStream(); // BufferedReader input = new BufferedReader(new InputStreamReader(inputStream)); // String ss=null; // while ((ss=input.readLine())!=null){ // System.out.println(ss); // } process.waitFor(); System.out.println("一直阻塞無法執行到這一步"); }
2、通過下面代碼證明上面的觀點:當將流中數據讀取完畢後waitFor不會阻塞,可執行下一步
public static void main(String[] args) throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder(); List<String> meta = new ArrayList<String>(); meta.add("ffmpeg"); meta.add("-i"); meta.add("C:/Users/Lenovo/Desktop/20200801134820261.mp3"); meta.add("-af"); meta.add("silencedetect=n=-1dB:d=0.5"); meta.add("-f"); meta.add("null"); meta.add("-"); processBuilder.command(meta); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); InputStream inputStream = process.getInputStream(); BufferedReader input = new BufferedReader(new InputStreamReader(inputStream)); String ss=null; while ((ss=input.readLine())!=null){ System.out.println(ss); } process.waitFor(); System.out.println("正常輸出"); }
3、與上面觀點產生矛盾的代碼:下面代碼中的流中仍然存在數據,但是waitFor並沒有陷入阻塞,推測原因可能是由於ipconfig與ffmpeg不同,不存在 持續向流中寫數據情況,因此waitFor可以正常結束阻塞
public static void main(String[] args) throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder(); List<String> meta = new ArrayList<String>(); meta.add("ipconfig"); processBuilder.command(meta); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); InputStream inputStream = process.getInputStream(); // BufferedReader input = new BufferedReader(new InputStreamReader(inputStream)); // String ss=null; // while ((ss=input.readLine())!=null){ // System.out.println(ss); // } process.waitFor(); System.out.println("正常輸出"); }
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 基於Process#waitFor()阻塞問題的解決
- 解決Process.getInputStream()阻塞的問題
- Java中Process類的使用與註意事項說明
- Java魔法堂之調用外部程序的方法
- Java Runtime的使用詳解