解决使用ProcessBuilder踩到的坑及注意事项
作者:monkey_win 发布时间:2023-11-24 01:37:55
使用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("正常输出");
}
来源:https://blog.csdn.net/monkey_win/article/details/103303174
猜你喜欢
- 前言假如有人问你这么几个问题,看能不能答上来Mybatis Mapper 接口没有实现类,怎么实现的 * JDK * 为什么不能对类进
- 算法描述堆排序算法的描述如下:将待排序的数组调整为最大堆,此时未排序的长度 N 为数组的长度,调整的过程就是倒序将数组的
- 一、链表1.1 概述链表是真正动态的数据结构,最简单的动态数据结构,基本用于辅助组成其他数据结构。数据存储在“节点”(Node)中优点:真正
- 开发 Web 应用的思路实现一个简单的 JSP/Servlet。搭建创建 Web 应用工程的环境。创建 Web 应用工程。Web 应用工程的
- 井字棋游戏要求在3乘3棋盘上,每行都相同或者每列都相同再或者对角线相同,则胜出.因此我们可以使用一个二维数组来表示棋盘,判断胜负只需要判断数
- 简介对于一个APP来说,肯定会有一个AppBar,这个AppBar一般包含了APP的导航信息等。虽然我们可以用一个固定的组件来做为AppBa
- 前言Java虽然五脏俱全但总有软肋,譬如获取CPU等硬件信息,当然我们可以通过JNI调用C/C++来获取,但对于对C/C++和Windows
- 什么是ServletContext?根据字面意思即Servlet上下文服务器会为每一个工程创建一个对象,这个对象就是ServletConte
- java字段值为null,不返回该字段类上打注解@JsonSerialize(include = JsonSerialize.Inclusi
- 半路开始看的朋友可以回顾一下前几篇java并发编程专题(一)----线程基础知识java并发编程专题(二)----如何创建并运行java线程
- 前言本文给你提供在Spring Boot 应用程序中编写好的单元测试的机制,并且深入技术细节。我们将带你学习如何以可测试的方式创建Sprin
- 在IntelliJ IDEA 中这个查看一个类也就是当前类的所有继承关系,包括实现的所有的接口和继承的类,这个继承,不仅仅是一级的继承关系,
- 初学线程时,总是将 run 方法和 start 方法搞混,虽然二者是完全不同的两个方法,但刚开始使用时很难分清,原因就是因为初次使用时效果貌
- 概述:App几乎都离不开与服务器的交互,本文主要讲解了flutter网络请求三种方式 flutter自带的HttpClient、 第三方库h
- 一、电子邮件详解假设自己的电子邮件是me@163.com,对方的邮件是you@163.com我们编写好文件填写好对方文件,点击发送,这些电子
- 前言之前我们提到了 CustomPaint er 的 Paint 可以使用渐变(GradientShader)来填充绘制的图形,本篇我们来介
- google benchmark已经为我们提供了类似的功能,而且使用相当简单。具体的解释在后面,我们先来看几个例子,我们人为制造几个时间复杂
- 目录ShutdownHook介绍ShutdownHook原理ShutdownHook的数据结构与执行顺序ShutdownHook触发点Shu
- 本文实例为大家分享了Android绝对布局AbsoluteLayout的具体代码,供大家参考,具体内容如下1>AbsoluteLayo
- Jackson反序列化遇到的问题最近在项目中需要使用Jackson把前台转来的字符转为对象,转换过程中发生了错误,报错如下com.faste