欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-08 20:46:55
使用processbuilder踩到的坑最近使用processbuilder执行命令,命令内容正确,但始终报错命令实行失败,是因为不熟悉processbuilder用法踩到了坑,记录一下。先看一下我模...

使用processbuilder踩到的坑

最近使用processbuilder执行命令,命令内容正确,但始终报错命令实行失败,是因为不熟悉processbuilder用法踩到了坑,记录一下。

先看一下我模拟出来的错误

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

要执行的命令:cp -rf /tmp/monkey/a.log /home/monkey/ 简单的cp命令拷贝一个文件,却报错说文件不存在。确认过文件确实存在该目录下。

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

查看jdk 中,我使用的processbuilder(***) 源码实现如下,并不是一个单独的字符串string形式,而是支持多个字符串,同时还有list集合方式。

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

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

于是想到会不会是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方法,将命令的内容组成字符串的形式执行的结果,而文章第一张图则是直接当做一条完整命令的执行结果。

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

至于为什么不能好有空格暂时未做深入了解,有带佬可以释疑吗?难道一条完整的命令当做一个字符串它不香嘛?

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("正常输出");
    }

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