记录一下java在windows和linux场景下,执行命令(cmd命令,bash命令,sh命令)的操作踩坑

在windows下,java调用cmd需要防止阻塞。而且有三种场景:

1.直接执行单句的cmd命令。

2.执行命令集.bat文件(这种跟情况1其实一样,写好.bat文件即可)。

3.在一个会话中交互式执行多句命令(当然这个做不到真正交互,只是可以模拟一句一句输入)。

直接给结果:

package xxx

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import cn.hutool.core.util.StrUtil;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static lombok.AccessLevel.PRIVATE;

/**
 * windows/Linux下执行命令的工具
 *
 * @author xxx
 * @since 0000/00/00
 **/
@Slf4j
@NoArgsConstructor(access = PRIVATE)
public class CommandExecutionUtil {
    /** 设定线程名称格式 */
    private static ThreadFactory factory = new ThreadFactoryBuilder().setNamePrefix("read echo task-").build();

    /** 执行程序需要同时读取掉两个回显流(执行结果和错误结果) */
    private static ThreadPoolExecutor executor
            = new ThreadPoolExecutor(6, 60, 60, TimeUnit.SECONDS, new SynchronousQueue<>(), factory);

    /**
     * 直接执行(通过bash来执行命令 比如./xxxxx.sh)
     *
     * @param command sh文件+参数,空格分开
     * @param timeoutSeconds 等待超时时间
     * @return 执行结果
     */
    public static List<String> executeLinuxSh(String command, long timeoutSeconds) {
        log.info("the execute command is: {}", command);
        if (StrUtil.isEmpty(command)) {
            log.warn("the execute command is empty.");
            return Collections.emptyList();
        }
        Process process = null;
        List<String> outInfos = Collections.emptyList();
        try {
            process = Runtime.getRuntime().exec(command);
            outInfos = getEchoInfos(process.getInputStream(), process.getErrorStream(), timeoutSeconds);
        } catch (Exception e) {
            log.error("System execute command failed!", e);
        } finally {
            if (Objects.nonNull(process)) {
                process.destroy();
            }
        }
        return outInfos;
    }

    /**
     * linux场景执行命令(通过sh来执行  sh xxxxx.sh形式)
     *
     * @param command command 执行的命令
     * @param timeoutSeconds 等待超时时间
     * @return 执行结果
     */
    public static List<String> executeLinuxCommandWaitTimeout(String command, long timeoutSeconds) {
        if (StrUtil.isEmpty(command)) {
            log.warn("the execute command is empty.");
            return Collections.emptyList();
        }
        String[] cmdArr = {"/bin/sh", "-c", command};
        Process process = null;
        List<String> outInfos = Collections.emptyList();
        try {
            process = Runtime.getRuntime().exec(cmdArr);
            outInfos = getEchoInfos(process.getInputStream(), process.getErrorStream(), timeoutSeconds);
        } catch (Exception e) {
            log.error("System execute command failed!", e);
        } finally {
            if (Objects.nonNull(process)) {
                process.destroy();
            }
        }
        return outInfos;
    }

    /**
     * 执行命令附加超时时间
     *
     * @param command 命令
     * @param timeoutSeconds 超时时间
     * @return 执行结果
     */
    public static List<String> executeWinCommandWaitTimeout(String command, long timeoutSeconds) {
        if (StrUtil.isEmpty(command)) {
            log.warn("the execute command is empty.");
            return Collections.emptyList();
        }
        try {
            return executeCommand("cmd /c " + command, timeoutSeconds);
        } catch (Exception e) {
            log.error("System execute command failed!", e);
        }
        return Collections.emptyList();
    }

    /**
     * 在windows场景下,执行单个cmd命令
     *
     * @param command windows场景单行命令
     * @return 执行结果文本
     * @throws IOException          IO异常
     * @throws ExecutionException   执行异常
     * @throws InterruptedException 记时异常
     * @throws TimeoutException     超时异常
     */
    private static List<String> executeCommand(String command, long timeoutSeconds)
            throws IOException, ExecutionException,
            InterruptedException, TimeoutException {
        Process process = Runtime.getRuntime().exec(command);
        // 命令执行错误回显流、命令执行的回显流
        List<String> outInfos = getEchoInfos(process.getInputStream(), process.getErrorStream(), timeoutSeconds);
        process.destroy();
        return outInfos;
    }

    /**
     * 执行windows的多个cmd命令(类似脚本执行)
     *
     * @param commands 多个cmd命令
     * @return 执行结果
     */
    public static List<String> executeWinCommands(List<String> commands) {
        if (CollectionUtil.isEmpty(commands)) {
            log.warn("the execute commands is empty.");
            return Collections.emptyList();
        }
        try {
            return executeCommands(commands);
        } catch (Exception e) {
            log.error("System execute commands failed!", e);
        }
        return Collections.emptyList();
    }

    /**
     * 在不能使用.bat(windows场景)脚本的场景下,可以使用此方法,执行脚本(脚本语句拆分到list里面)
     *
     * @param commands 脚本语句,list里面一句相当于一行
     * @return 执行后的交互结果文本
     * @throws IOException          IO异常
     * @throws ExecutionException   执行异常
     * @throws InterruptedException 记时异常
     * @throws TimeoutException     超时异常
     */
    private static List<String> executeCommands(List<String> commands) throws IOException, ExecutionException,
            InterruptedException, TimeoutException {
        Process process = Runtime.getRuntime().exec("cmd");
        // 同一个会话命令持续输入流
        try (OutputStream processIn = process.getOutputStream();
             OutputStreamWriter osw = new OutputStreamWriter(processIn);
             BufferedWriter bw = new BufferedWriter(osw)) {
            for (String command : commands) {
                bw.write(command);
                bw.newLine();
            }
            bw.flush();
        }
        // 命令执行的回显流、命令执行错误回显流
        List<String> outInfos = getEchoInfos(process.getInputStream(), process.getErrorStream(), 30);
        process.destroy();
        return outInfos;
    }

    private static List<String> getEchoInfos(InputStream processOut, InputStream processErr, long timeoutSeconds) throws ExecutionException,
            InterruptedException, TimeoutException {
        Future<List<String>> outFuture = executor.submit(() -> readEcho(processOut));
        Future<List<String>> errFuture = executor.submit(() -> readEcho(processErr));
        List<String> outInfos = outFuture.get(timeoutSeconds, TimeUnit.SECONDS);
        List<String> errInfos = errFuture.get(timeoutSeconds, TimeUnit.SECONDS);
        if (CollectionUtil.isNotEmpty(errInfos)) {
            log.warn("execute commands error: {}", errInfos);
        }
        return outInfos;
    }

    private static List<String> readEcho(InputStream echoStream) throws IOException {
        try (InputStream is = echoStream;
             InputStreamReader isr = new InputStreamReader(is, ApplicationContext.isWindows() ? Charset.forName("GBK") : StandardCharsets.UTF_8);
             BufferedReader br = new BufferedReader(isr)) {
            List<String> echoInfos = new ArrayList<>();
            String echoInfo;
            while ((echoInfo = br.readLine()) != null) {
                echoInfos.add(echoInfo);
            }
            return echoInfos;
        }
    }
}

还有一种特殊的场景,将java程序注册成windows的SYSTEM权限的服务时,调用cmd命令是有权限问题的,比如想启动一个exe,执行start xxx.exe只能启动一个没有窗口的后台,因为SYSTEM权限不会调用到窗口,没有界面。

这种情况,需要用C/C++写一个通过管理员权限启动一个进程的函数。打包dll文件,java通过jna方式调用此dll,并传入要执行的命令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值