解决使用ProcessBuilder踩到的坑及注意事项

 更新时间:2021年06月08日 15:26:08   作者:monkey_win  
这篇文章主要介绍了解决使用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("正常输出");
    }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java中几种http请求方式示例详解

    java中几种http请求方式示例详解

    在日常工作和学习中有很多地方都需要发送HTTP请求,下面这篇文章主要给大家介绍了关于java中几种http请求方式的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-11-11
  • java Spring 5 新特性函数式Web框架详细介绍

    java Spring 5 新特性函数式Web框架详细介绍

    正如昨天Juergen博客中所提到的,Spring 5.0的第二个里程碑是引入了一个新的函数式web框架。在这篇文章中,我们将给出关于这个框架的更多信息,,需要的朋友可以参考下
    2016-12-12
  • 关于Java多线程上下文切换的总结

    关于Java多线程上下文切换的总结

    CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换,需要的朋友可以参考下
    2023-05-05
  • 浅析我对 String、StringBuilder、StringBuffer 的理解

    浅析我对 String、StringBuilder、StringBuffer 的理解

    StringBuilder、StringBuffer 和 String 一样,都是用于存储字符串的。这篇文章谈谈小编对String、StringBuilder、StringBuffer 的理解,感兴趣的朋友跟随小编一起看看吧
    2020-05-05
  • SpringCache的简介和使用教程

    SpringCache的简介和使用教程

    缓存是实际工作中经常使用的一种提高性能的方法, 我们会在很多场景下来使用缓存,而spring-cache就是一种简单的实现。通过本文学习可以了解SpringCache的简介和使用方法,感兴趣的朋友一起看看吧
    2021-11-11
  • mybatis-plus  mapper中foreach循环操作代码详解(新增或修改)

    mybatis-plus mapper中foreach循环操作代码详解(新增或修改)

    这篇文章主要介绍了mybatis-plus mapper中foreach循环操作代码详解(新增或修改),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • 使用Java WebSocket获取客户端IP地址的示例代码

    使用Java WebSocket获取客户端IP地址的示例代码

    在开发Web应用程序时,我们通常需要获取客户端的 IP 地址用于日志记录、身份验证、限制访问等操作,本文将介绍如何使用Java WebSocket API获取客户端IP地址,以及如何在常见的WebSocket框架中获得客户端 IP地址,需要的朋友可以参考下
    2023-11-11
  • Java 添加、删除、格式化Word中的图片步骤详解( 基于Spire.Cloud.SDK for Java )

    Java 添加、删除、格式化Word中的图片步骤详解( 基于Spire.Cloud.SDK for Java )

    这篇文章主要介绍了Java 添加、删除、格式化Word中的图片( 基于Spire.Cloud.SDK for Java ),本文分步骤通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • java面向对象设计原则之开闭原则示例解析

    java面向对象设计原则之开闭原则示例解析

    这篇文章主要介绍了java面向对象设计原则之开闭原则的示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2021-10-10
  • SpringBoot 使用定时任务(SpringTask)的详细步骤

    SpringBoot 使用定时任务(SpringTask)的详细步骤

    Cron 表达式非常灵活,可以满足各种定时任务的需求,但需要注意的是,Cron 表达式只能表示固定的时间点,无法处理复杂的时间逻辑,本文给大家介绍SpringBoot 使用定时任务(SpringTask)的详细步骤,感兴趣的朋友一起看看吧
    2024-02-02

最新评论