java 执行linux 命令

文章目录

  • 前言
  • 一、linux命令执行
  • 二、使用步骤
  • 三、踩坑

前言

java 执行linux 命令;
本文模拟复制linux文件到指定文件夹后打zip包后返回zip名称,提供给下载接口下载zip;

一、linux命令执行

linux命令执行Process process = Runtime.getRuntime().execProcess process = new ProcessBuilder(commands).start();

   
    /**
     * 执行Linux命令
     */
    public static void execCommand(String commands) throws IOException {
        Process process = null;
        BufferedReader reader = null;
        try {
            //创建进程实例执行Linux命令
            process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", commands});
            // 获取标准输入流
            reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                //log.info("linux----" + line);
            }
            // 等待进程结束
            int exitCode = process.waitFor();
            if (exitCode != 0) {
                log.error("执行Linux命令异常,exitCode={},linux command={}", exitCode, commands);
                throw new BusinessCheckException("执行Linux命令异常");
            }
        } catch (IOException | InterruptedException e) {
            log.error("执行Linux命令异常", e);
            throw new BusinessCheckException("执行Linux命令异常");
        } finally {
            if (reader != null) {
                reader.close();
            }
            if (process != null) {
                process.destroy();
            }
        }
    }

二、使用步骤

  1. 通过linux命令,打包目标数据到zip
public String downToZip(CorpQrCodeReq req, HttpServletResponse response) {
   		StopWatch sw = new StopWatch();
		// 参数校验
        if (CollectionUtils.isEmpty(req.getCorpIds()) || CollectionUtils.isEmpty(req.getCorpNames())) {
            return "0";
        }
        // 生成保存目录文件夹
        String uuid = UUID.randomUUID().toString().replace("-", "");
        String baseUploadPath = "/tmp/zip/"+uuid+"/";
        FileUtil.mkdir(baseUploadPath);
        try {
            //生成二维码到临时目录
            List<String> enterpriseIds = req.getCorpIds();
            List<String> enterpriseNames = req.getCorpNames();
            List<String> command = new ArrayList<>(enterpriseIds.size());
            // 拼接被复制的文件地址
            for (int i = 0; i < enterpriseIds.size(); i++) {
                String path = this.qrCode(CorpQrCodeReq.builder()
                        .corpIds(Lists.newArrayList(enterpriseIds.get(i)))
                        .corpNames(Lists.newArrayList(enterpriseNames.get(i))).build());
                command.add(path + " ");
            }
            sw.stop();
            log.info(sw.getLastTaskName() + sw.getTotalTimeSeconds() + "s");
            sw.start("执行cp命令");
            String property = System.getProperty("line.separator");
            //拼接shell脚本
            String sb = " for i in  " + String.join(" ", command) +
                    property +
                    "do" +
                    property +
                    " cp -n  \"$i\" " + baseUploadPath +
                    property +
                    "done";
            //执行cp命令
            execCommand(sb);
            sw.stop();
            log.info(sw.getLastTaskName() + sw.getTotalTimeSeconds() + "s");
            sw.start("执行压缩命令");
            //执行压缩命令 统一压缩比一个一个压缩进去效率高 
            //-j忽略源文件路径,直接打包目标文件
            String zipCommand = " /bin/zip -1 -j " + baseUploadPath.concat(".zip") + " " + baseUploadPath + "/*";
            execCommand(zipCommand);
            sw.stop();
            log.info(sw.getLastTaskName() + sw.getTotalTimeSeconds() + "s");
            log.info(sw.prettyPrint());
            return uuid;
        } catch (Exception e) {
            log.error("压缩包下载失败 ", e);
            throw new BusinessCheckException("压缩包下载失败");
        }
    }
  1. 根据uuid,下载zip文件(2个接口,一个是根据条件生成zip,另一个根据zip名称下载),此处下载应用了nginx的X-Accel-Redirect,具体使用方法参考springboot X-Accel-Redirect 大文件下载实现
    public void downZip(String path, HttpServletResponse response) {
        try {
            String baseUploadPath ="/tmp/zip/";
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("企业二维码", StandardCharsets.UTF_8.toString()) + ".zip");
            //设置URI给nginx进行内部的跳转
            response.setHeader("X-Accel-Redirect", "/upload" + baseUploadPath.concat(path).concat(".zip"));
        } catch (Exception e) {
            log.error(" 二维码压缩包下载失败 ", e);
            throw new BusinessCheckException("二维码压缩包下载失败");
        }
    }

三、踩坑

  1. linux 和 java 2个进程异步问题

linux命令执行后,和java服务是2个进程。
当linux命令执行过程期间,java下面的业务服务已触发时(比如文件数量较大,而下载接口触发较快时)会造成数据不完整(zip包打包不全,一般一个文件没有)。
此时我们需要利用 读取执行流 + process.waitFor(),等待linux进程结束后再做业务处理。

  1. 关于Process初始化
 Process process = null;
 // 如果commands拼接的命令中包含空格 会自动识别为2段命令
 process = new ProcessBuilder(commands).start();
 // 不支持空格和|
 process = Runtime.getRuntime().exec(commands);
 // -c 表示cmd是一条命令,不会被截断
 process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", commands});
  1. linux for cp 空格识别问题
    执行脚本如下:
for i in  "/data/mnt/www/corp-qr/769847/九台区九台中由 生活服务部.jpg"
do
 cp -n  $i  /data/zip/5e0e67f59c524a4e9851abd3a3dfe0ac
done

此时执行命令报错:cp: 无法获取"/data/mnt/www/corp-qr/769847/九台区九台中由" 的文件状态(stat): 没有那个文件或目录 cp: 无法获取"生活服务部.jpg" 的文件状态(stat): 没有那个文件或目录,linux 会将目标文件解析成2个文件。

解决方案:

"/data/mnt/www/corp-qr/769847/九台区九台中由 生活服务部.jpg" 修改为 "/data/mnt/www/corp-qr/769847/*"cp -n  $i  /data/zip/5e0e67f59c524a4e9851abd3a3dfe0ac 修改为 cp -n  "$i"  /data/zip/5e0e67f59c524a4e9851abd3a3dfe0ac
  1. 执行效率优化
    一千个文件测试结果:
    a. 文件循环时直接打包到zip"/bin/zip -rj " + baseUploadPath.concat(".zip") + " " + baseUploadPath + "/*";效率不如cp 到文件夹下压缩文件夹
    b. 调整压缩率 zip -1
    c. 文件循环时直接cp,由于execCommand执行linux命令要等返回结果再执行,所以效率也不高

  2. cp 文件覆盖问题
    同名文件cp linux会提示是否覆盖,如果通过java执行cp,无法给予是否覆盖的回应,报错:没有那个文件或目录,如果忽略覆盖提示?
    覆盖提示原因:
    java 执行linux 命令_第1张图片
    我们执行cp时 实际linux执行的是cp -i -i 表示覆盖前提示

-- 命令前加反斜线忽略alias
\cp /var/tmp/test.txt /tmp 
-- 使用命令全路径
/bin/cp /var/tmp/test.txt /tmp
-- 先取消别名再复制(不推荐)
unalias cp
-- 不覆盖
cp -n /var/tmp/test.txt /tmp

java 执行linux 命令_第2张图片

你可能感兴趣的:(【Java技术】,java,linux,windows)