java如何执行linux命令
作者:Mr-Wanter
这篇文章主要介绍了java如何执行linux命令问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
前言
java 执行linux 命令;
本文模拟复制linux文件到指定文件夹后打zip包后返回zip名称,提供给下载接口下载zip;
一、linux命令执行
linux命令执行
Process process = Runtime.getRuntime().exec
或
Process 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("压缩包下载失败");
}
}
2.根据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进程结束后再做业务处理。
2.关于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});
3.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
5.执行效率优化
一千个文件测试结果:
- a. 文件循环时直接打包到zip
"/bin/zip -rj " + baseUploadPath.concat(".zip") + " " + baseUploadPath + "/*";效率不如cp到文件夹下压缩文件夹 - b. 调整压缩率
zip -1 - c. 文件循环时直接cp,由于
execCommand执行linux命令要等返回结果再执行,所以效率也不高
6.cp 文件覆盖问题
同名文件cp linux会提示是否覆盖,如果通过java执行cp,无法给予是否覆盖的回应,报错:没有那个文件或目录,如果忽略覆盖提示?
覆盖提示原因:

我们执行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
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
