2009/3/22

神奇的 Runtime.exec 遇到保留字元的處理方法

要產生Process執行command line程式,在java裡面是使用Runtime.exec,在JDK 1.5之後出現了另一個 ProcessBuilder,ProcessBuilder多了管理環境變數的方法,也能切換工作目錄。一般使用Runtime Process會先遇到IO Blocking的問題,這個可以參考這些文章:When Runtime.exec() won'tjava中呼叫.bat 卻沒有反應.... StreamGobbler的詳細解釋,使用 StreamGobbler 解決。但另外遇到 shell command 裡面的保留字元時,就會產生了一些奇怪的問題。

最常見的就是空白字元,例如在命令的執行路徑上,出現了空白字元,以 windows 來說,如果直接在"命令提示字元"中,打上 c:\Program Files\xxx\xxx.exe,就會出現路徑的錯誤,因為shell沒辦法處理有空白字元的路徑,所以要執行的時候,就得加上 雙引號 變成 "c:\Program Files\xxx\xxx.exe",這樣就可以執行了。

在Linux環境也是一樣的,遇到空白字元,就會發生問題,除了用 雙引號的方法之外,單引號也可以,或是加上escape char \,例如 /home/root/bin/xx x.sh ,可以改成 "/home/root/bin/xx x.sh" 或是 '/home/root/bin/xx x.sh' 或是在空白前面加上escape char /home/root/bin/xx\ x.sh 就可以執行了。

事實上除了空白字元之外,還有可能會遇到 ( ) " ' 這些字元,都是shell上的保留字元。為了求得一個能同時在 linux 與 windows 都能運作的執行方法,用雙引號似乎就能同時解決這樣的問題,但是事實上,放到 Runtim.exec 裡面執行時,又不是這麼一回事。為了執行一個程式,現在有幾種寫法可以用,(1) 將命令含參數,弄成一個字串,然後放到 runtime.exec 執行 (2) 將命令含參數,弄成一個字串,然後前面加上 shell ,例如windows 平台就寫 cmd /c、而linux 平台就寫 /bin/sh -c,然後放到 runtime.exec 執行 (3) 將命令與參數作成 List shellcmd,然後用 new ProcessBuilder(shellcmd).start() 執行 (4) 將命令與參數作成 List shellcmd ,前面加上 shell,cmd /c 或是 /bin/sh -c,然後用 new ProcessBuilder(shellcmd).start() 執行。

測試許久,最後還是沒有辦法找到一種方式,同時適用於 windows 與 linux,結果以 ffmpeg bin\ffmpeg.exe 與 ffmpeg bin\ffmpeg 兩個執行檔為例,假設我要執行 ffmpeg -i test.wmv -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 test.flv,在 windows平台,我得寫

List cmdlist=new ArrayList();
cmdlist.add("\"ffmpeg bin\ffmpeg\"");
cmdlist.add("-i");
cmdlist.add("\"test.wmv\""); // 因為檔案路徑有可能會含有空白字元,就幫他加上雙引號
cmdlist.add("-ab");
cmdlist.add("56");
cmdlist.add("-ar");
cmdlist.add("22050");
cmdlist.add("-b");
cmdlist.add("500");
cmdlist.add("-r");
cmdlist.add("15");
cmdlist.add("-s");
cmdlist.add("320x240");
cmdlist.add("\"test.flv\""); // 因為檔案路徑有可能會含有空白字元,就幫他加上雙引號
new ProcessBuilder(cmdlist).start();

而在 linux平台,我得寫

List cmdlist=new ArrayList();
cmdlist.add("/bin/sh");
cmdlist.add("-c");
cmdlist.add("\"ffmpeg bin\ffmpeg\" -i \"test.wmv\" -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 \"test.flv\"
"); // 因為檔案路徑有可能會含有空白字元,就幫他加上雙引號
new ProcessBuilder(cmdlist).start();