解決使用ProcessBuilder踩到的坑及注意事項(xiàng)
使用ProcessBuilder踩到的坑
最近使用ProcessBuilder執(zhí)行命令,命令內(nèi)容正確,但始終報(bào)錯(cuò)命令實(shí)行失敗,是因?yàn)椴皇煜rocessBuilder用法踩到了坑,記錄一下。
先看一下我模擬出來(lái)的錯(cuò)誤

要執(zhí)行的命令:cp -rf /tmp/monkey/a.log /home/monkey/ 簡(jiǎn)單的cp命令拷貝一個(gè)文件,卻報(bào)錯(cuò)說(shuō)文件不存在。確認(rèn)過(guò)文件確實(shí)存在該目錄下。

查看jdk 中,我使用的ProcessBuilder(***) 源碼實(shí)現(xiàn)如下,并不是一個(gè)單獨(dú)的字符串String形式,而是支持多個(gè)字符串,同時(shí)還有List集合方式。


于是想到會(huì)不會(huì)是ProcessBuilderbuilder不支持包含空格的命令。
動(dòng)手寫了下面的代碼進(jìn)行測(cè)試
public class ProcessBuilderDemo {
/**
* 測(cè)試processBuilder執(zhí)行cp命令
* cp /tmp/monkey/a.log /home/monkey/
* 源路徑 args[1]: /tmp/monkey/a.log
* 目標(biāo)路徑 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);
}
}
/**
* 執(zhí)行命令,命令用拼接成一個(gè)字符串形式(會(huì)包含空格)
* @param src 源路徑
* @param tag 目標(biāo)路徑
*/
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());
}
}
/**
* 執(zhí)行命令,命令各個(gè)部分拼接成一個(gè)數(shù)組或者ArrayList集合
* 該方法采用數(shù)組實(shí)現(xiàn)
* @param src 源路徑
* @param tag 目標(biāo)路徑
*/
private static void cmdIsListOrArray(String src, String tag) {
String cmd = "cp";
// 命令的各個(gè)部分組成一個(gè)字符串?dāng)?shù)組,用該數(shù)組創(chuàng)建ProcessBuilder對(duì)象
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());
}
}
}
果然如我所猜想的一樣:包含有空格的命令執(zhí)行會(huì)報(bào)錯(cuò)。
以下是cmdIsListOrArray方法,將命令的內(nèi)容組成字符串的形式執(zhí)行的結(jié)果,而文章第一張圖則是直接當(dāng)做一條完整命令的執(zhí)行結(jié)果。

至于為什么不能好有空格暫時(shí)未做深入了解,有帶佬可以釋疑嗎?難道一條完整的命令當(dāng)做一個(gè)字符串它不香嘛?
while(true) {
伸手黨;
}
使用ProcessBuilder執(zhí)行本地程序的注意事項(xiàng)
錯(cuò)誤代碼
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類帶參執(zhí)行命令容易出現(xiàn)的兩個(gè)坑
1、執(zhí)行后沒(méi)有任何反映
原因?yàn)橥ㄟ^(guò)ProcessBuilder運(yùn)行的參數(shù)還沒(méi)有執(zhí)行完畢程序就退出了。
通過(guò)if(process.isAlive()){process.waitFor();}可以規(guī)避此問(wèn)題,但是需要注意waitFor時(shí)程序時(shí)阻塞的,如果是持續(xù)運(yùn)行的web項(xiàng)目可以通過(guò)開啟子線程來(lái)執(zhí)行ProcessBuilder
2、執(zhí)行后沒(méi)有任何輸出
最惡心的地方,除了getInputStream外還有一個(gè)getErrorStream也可以獲取數(shù)據(jù),而且一般執(zhí)行的程序數(shù)據(jù)都會(huì)輸出在getErrorStream中,所以getInputStream無(wú)法獲取到數(shù)據(jù)
處理后的代碼
public static void main(String[] args) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder("java","-version");
processBuilder.redirectErrorStream(true);//將錯(cuò)誤流中的數(shù)據(jù)合并到輸入流
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);
}
}
后續(xù)發(fā)現(xiàn)新的問(wèn)題,當(dāng)某個(gè)軟件會(huì)持續(xù)向流中寫數(shù)據(jù),這時(shí)流中數(shù)據(jù)沒(méi)有被讀取完畢(流中存在數(shù)據(jù)【測(cè)試發(fā)現(xiàn)流中存在數(shù)據(jù)并不是一定會(huì)阻塞】),會(huì)導(dǎo)致waitFor一直陷入阻塞
上述問(wèn)題處理后的代碼(正確使用ProcessBuilder的代碼)
public static void main(String[] args) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder("java","-version");
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
// 通過(guò)標(biāo)準(zhǔn)輸入流來(lái)拿到正常和錯(cuò)誤的信息
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();
}
復(fù)現(xiàn)錯(cuò)誤:
1、某個(gè)軟件持續(xù)向流中寫數(shù)據(jù)時(shí),如果流中數(shù)據(jù)未被讀取完畢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("一直阻塞無(wú)法執(zhí)行到這一步");
}
2、通過(guò)下面代碼證明上面的觀點(diǎn):當(dāng)將流中數(shù)據(jù)讀取完畢后waitFor不會(huì)阻塞,可執(zhí)行下一步
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、與上面觀點(diǎn)產(chǎn)生矛盾的代碼:下面代碼中的流中仍然存在數(shù)據(jù),但是waitFor并沒(méi)有陷入阻塞,推測(cè)原因可能是由于ipconfig與ffmpeg不同,不存在 持續(xù)向流中寫數(shù)據(jù)情況,因此waitFor可以正常結(jié)束阻塞
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("正常輸出");
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
AsyncHttpClient?RequestFilter請(qǐng)求篩選源碼解讀
這篇文章主要為大家介紹了AsyncHttpClient?RequestFilter請(qǐng)求篩選源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Java全面細(xì)致講解Cookie與Session及kaptcha驗(yàn)證碼的使用
web開發(fā)階段我們主要是瀏覽器和服務(wù)器之間來(lái)進(jìn)行交互。瀏覽器和服務(wù)器之間的交互就像人和人之間進(jìn)行交流一樣,但是對(duì)于機(jī)器來(lái)說(shuō),在一次請(qǐng)求之間只是會(huì)攜帶著本次請(qǐng)求的數(shù)據(jù)的,但是可能多次請(qǐng)求之間是會(huì)有聯(lián)系的,所以提供了會(huì)話機(jī)制2022-06-06
Mybatisplus實(shí)現(xiàn)JSON處理器的示例代碼
Mybatisplusjson是基于Mybatisplus開發(fā)的一個(gè)json工具庫(kù),本文主要介紹了Mybatisplus實(shí)現(xiàn)JSON處理器的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
java實(shí)現(xiàn)投票程序設(shè)計(jì)
這篇文章主要介紹了java實(shí)現(xiàn)投票程序設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-12-12
解決Mybatis-Plus更新方法不更新NULL字段的問(wèn)題
這篇文章主要介紹了解決Mybatis-Plus更新方法不更新NULL字段的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
Web三大組件之Filter,Listener和Servlet詳解
這篇文章主要為大家詳細(xì)介紹了Web三大組件之Filter,Listener和Servlet,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
在SpringBoot環(huán)境中使用Mockito進(jìn)行單元測(cè)試的示例詳解
Mockito是一個(gè)流行的Java?mocking框架,它允許開發(fā)者以簡(jiǎn)單直觀的方式創(chuàng)建和使用模擬對(duì)象(mocks),Mockito特別適用于在Spring?Boot環(huán)境中進(jìn)行單元測(cè)試,所以本文介紹了在SpringBoot環(huán)境中使用Mockito進(jìn)行單元測(cè)試的示例,需要的朋友可以參考下2024-11-11
Java 文創(chuàng)商城系統(tǒng)的實(shí)現(xiàn)流程
讀萬(wàn)卷書不如行萬(wàn)里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+mysql+maven+tomcat實(shí)現(xiàn)一個(gè)文創(chuàng)商城系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2021-11-11
RabbitMQ消息隊(duì)列的目錄結(jié)構(gòu)
這篇文章主要介紹了RabbitMQ消息隊(duì)列的目錄結(jié)構(gòu),RabbitMQ?屬于消息中間件,主要用于組件之間的解耦,消息的發(fā)送者無(wú)需知道消息使用者的存在,反之亦然,那么用了那么久RabbitMQ,其目錄結(jié)構(gòu)是怎樣的呢,讓我們一起來(lái)看一下吧2023-08-08

