Java Runtime exec 执行shell命令小结

一 简介

在工作中需要java程序运行一些shell命令,可以使用java的Runtime来执行。该类主要提供以下方法来完成命令执行
Process exec(String command)
Process exec(String command, String[] envp)
Process exec(String command, String[] envp, File dir)

Process exec(String cmdarray[])
Process exec(String[] cmdarray, String[] envp)
Process exec(String[] cmdarray, String[] envp, File dir)

exec 的方法说明中有几个要点可关注,以下问题也是和这几点相关
1.Executes the specified string command in a separate process.
在分开的(新的)进程执行命令
2、return an instance of a subclass of Process that can be used to control the process and obtain information about it
返回的Process ,是返回一个子进程
3、the created subprocess does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and getErrorStream(). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, or even deadlock.
创建的新的进程没有终端窗口的话,标准I/O会重定向到父进程中,如果缓冲区有限制的话,会导致锁或死锁(子进程写不进去,父进程一直等待子进程执行)。

其中返回 Process 的java文档说明如下

在这里插入图片描述

二 问题及解决方法

问题1 需要等待问题

int success = process.exitValue();// 0成功 1失败
没有得到预期结果,执行命令失败

原因

从简介中可以看到,该方法是启动新的进程来异步执行命令,可能新进程还没有结束,主进程已经继续往下执行了。

解决方法

添加等待进程执行完毕之后,继续执行
boolean wait = process.waitFor(2, TimeUnit.MINUTES);

问题2 卡死

有些命令正常,但当执行后有大量信息输出时,卡死。

原因

新进程执行命令时产生大量错误流或者标准流,由于子进程没有终端,会把流重定向到父进程中,当缓冲区较小时,会写入失败,父进程还在一直等待子进程结束才能继续,因此死锁了。

解决方法

启动新的线程来读取标准I/O.

 new Thread(() -> {
                BufferedReader b = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line = "";
                try {
                    while ((line = b.readLine()) != null) {
                        sb.append(line).append("\n");
                    }
                    b.close();
                    log.info("新线程获取输入流结束");
                } catch (Exception ex) {
                    log.error("获取输入流异常", ex);
                } finally {
                    latch.countDown();// 执行完毕后 减掉数量
                }
            }).start();
            // 新启动线程 错误流直接输出出去 以免阻塞缓冲区
            new Thread(() -> {
                BufferedReader b = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                String line = "";
                try {
                    while ((line = b.readLine()) != null) {
                        log.error("错误流输出:" + line);
                    }
                } catch (Exception ex) {
                    log.error("获取错误流异常", ex);
                }
            }).start();

问题3 命令返回值偶发不准

获取的标注输出内容可能不全。

原因

按问题2的解决方式,获取标准流采用新线程,但这时是异步处理,当下边主进程获取流的内容时,有可能新的线程还没执行完

解决方法

采用门闩方式处理。预期的线程全部执行完,才能进行后续操作。

三 最终工具类完整代码

public class ExecCmdUtil {
    private ExecCmdUtil() {
    }

    public static AIPRet<String> exec(String cmd) {
        String result;
        StringBuilder sb = new StringBuilder();
        log.info("即将执行命令:" + cmd);
        try {
            Process process = Runtime.getRuntime().exec(cmd);
            final CountDownLatch latch = new CountDownLatch(1);// 来个门闩,当获取输入流进程结束时才可以继续执行后续步骤
            // 启动新线程 获取标准输入流
            new Thread(() -> {
                BufferedReader b = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line = "";
                try {
                    while ((line = b.readLine()) != null) {
                        sb.append(line).append("\n");
                    }
                    b.close();
                    log.info("新线程获取输入流结束");
                } catch (Exception ex) {
                    log.error("获取输入流异常", ex);
                } finally {
                    latch.countDown();// 执行完毕后 减掉数量
                }
            }).start();
            // 新启动线程 错误流直接输出出去 以免阻塞缓冲区
            new Thread(() -> {
                BufferedReader b = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                String line = "";
                try {
                    while ((line = b.readLine()) != null) {
                        log.error("错误流输出:" + line);
                    }
                } catch (Exception ex) {
                    log.error("获取错误流异常", ex);
                }
            }).start();

            boolean await = latch.await(2, TimeUnit.MINUTES);// 等待输入流获取完成,所有门闩就执行完
            log.info("异步线程门闩等待结果latch.await:" + await);

            boolean wait = process.waitFor(2, TimeUnit.MINUTES);
            log.info("处理等待结果waitFor:" + wait);

            int success = process.exitValue();// 0成功 1失败
            log.info("处理退出结果exitValue:" + success);

            if (sb.length() > 0 && sb.lastIndexOf("\n") > 0) {
                result = sb.substring(0, sb.lastIndexOf("\n"));
            } else {
                result = sb.toString();
            }
            log.info("命令:" + cmd + "执行完毕,返回信息:" + result);
            if (0 == success) {
                return AIPRet.<String>builder()
                        .success(true)
                        .data(result)
                        .build();
            } else {
                return AIPRet.<String>builder()
                        .success(false)
                        .message(result)
                        .build();
            }
        } catch (InterruptedException ex) {
            log.error("执行命令异常:", ex);
            Thread.currentThread().interrupt();
            return AIPRet.<String>builder()
                    .success(false)
                    .message("执行命令" + cmd + "失败")
                    .build();
        } catch (Exception ex) {
            log.error("执行命令异常", ex);
            return AIPRet.<String>builder()
                    .success(false)
                    .message("执行命令" + cmd + "失败")
                    .build();
        }
    }


    public static AIPRet<String> execute(String[] cmdArray) {
        Runtime r = Runtime.getRuntime();
        String result = "";
        StringBuilder sb = new StringBuilder();
        try {
            log.info("======即将执行命令: " + Arrays.toString(cmdArray) + "=======");
            Process p = r.exec(cmdArray);
            final CountDownLatch latch = new CountDownLatch(1);// 来个门闩,当获取输入流进程结束时才可以继续执行后续步骤

            new Thread(() -> {
                BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream()));
                String line = "";
                try {
                    while ((line = b.readLine()) != null) {
                        sb.append(line).append("\n");
                    }
                    b.close();
                    log.info("新线程获取输入流结束");
                } catch (Exception ex) {
                    log.error("获取输入流异常", ex);
                } finally {
                    latch.countDown();// 执行完毕后 减掉数量
                }
            }).start();

            new Thread(() -> {
                BufferedReader b = new BufferedReader(new InputStreamReader(p.getErrorStream()));
                String line = "";
                try {
                    while ((line = b.readLine()) != null) {
                        log.error("错误流输出:" + line);
                    }
                } catch (Exception ex) {
                    log.error("获取错误流异常", ex);
                }
            }).start();


            boolean await = latch.await(2, TimeUnit.MINUTES);// 等待输入流获取完成,所有门闩就执行完
            log.info("异步线程门闩等待结果latch.await:" + await);

            boolean wait = p.waitFor(2, TimeUnit.MINUTES);
            log.info("执行等待结果waitFor:" + wait);
            int success = p.exitValue();// 0成功 1失败
            log.info("======执行命令结果exitValue:" + success + "==========");

            if (sb.length() > 0 && sb.lastIndexOf("\n") > 0) {
                result = sb.substring(0, sb.lastIndexOf("\n"));
            } else {
                result = sb.toString();
            }
            log.info("命令:" + Arrays.toString(cmdArray) + "执行完毕,返回信息:" + result);

            if (success != 0) {
                log.info("======执行命令返回信息:" + result + "==========");
                return AIPRet.<String>builder()
                        .success(false)
                        .message("执行命令" + Arrays.toString(cmdArray) + "失败" + result)
                        .build();
            } else {
                return AIPRet.<String>builder()
                        .success(true)
                        .data(result)
                        .build();
            }
        } catch (Exception ex) {
            log.error("执行命令异常:" + Arrays.toString(cmdArray), ex);
            Thread.currentThread().interrupt();
            return AIPRet.<String>builder()
                    .success(false)
                    .message("执行命令" + Arrays.toString(cmdArray) + "失败")
                    .build();
        }

    }

}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

祺稷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值