这是因为shell脚本被打进jar之后,不再以完整的.sh脚本存在,而是经过了打包程序的编译。
解决方法时:把shell脚本和jar包拷贝到同一个路径下,在代码中通过绝对路径引用shell脚本,进行执行。此过程注意为shell脚本赋权。
String path = System.getProperty("user.dir");
ProcessBuilder pb = new ProcessBuilder(command);
pb.directory(new File(path));
int runningStatus = 0;
try {
Process p = pb.start();
runningStatus = p.waitFor();
} catch (InterruptedException e) {
log.error("error:" + e.getLocalizedMessage());
} catch (IOException e) {
throw new RuntimeException(e);
}
在Docker中执行shell脚本时,要将shell脚本拷贝进镜像中。但是拷贝进去之后,发现报No such file or directory。仔细检查了一下,怀疑是因为没有为shell脚本增加权限。然后在Dockerfile中增加了如下内容:
RUN chmod u+x /opt/gansu_crontab_download_gfd_10D.sh
RUN chmod u+x /opt/delete_gansu_tempfile.sh
RUN chmod u+x /opt/zj_sh_crontab_download_gfd_10D.sh
RUN chmod u+x /opt/delete_zjsh_tempfile.sh
但是之后运行提示没有找到 ~/.bashsc 和 ~/.bash_profile。下面是我的shell脚本最初部分内容:
#!/bin/sh
set -x
source ~/.bashrc
source ~/.bash_profile
……
通过仔细查找发现是因为我引用的是openjdk:8-jdk-alpine的镜像,此镜像中是没有提供这些环境的。然后考虑安装bash,在Dockerfile中增加了如下内容:
# 安装bash
RUN apk add --no-cache bash
因为Docker中默认使用的是 /bin/sh,我又将代码中的命令改成了以 /bin/bash 调用:
ProcessBuilder pb = new ProcessBuilder("/bin/bash /opt/gansu_xxx.sh");
但是在这一步无论怎么搞都是无法运行,相反的,进入Docker容器内,直接./gansu_xxx.sh
就能直接运行。然后反复检查。最后发现我之前的shell脚本头是
#!/bin/sh
应该改为:
#!/bin/bash
只有这样才能正确被bash调用。到此程序终于开始运行了。但还是有一些问题。
我的shell脚本有获取前一天日期的脚本,但是无法获取,脚本如下:
DateInfo=$(date -d'-1 day' "+%Y%m%d")
但是在运行的时候,提示我语法不对。然后我仔细检查了一下发现 openjdk:8-jdk-alpine镜像用的shell命令是busybox的,它里面的date命令和传统的linux下的date有点区别,其中的 -d
后面的参数有些不一样。最终改成了下面的语法:
DateInfo=$(date -d@"$(( `date +%s`-86400))" +"%Y%m%d")
关于这个问题,可能也比较有特殊性。经过一番检查在我的shell脚本里有下面一段:
{
#一些业务逻辑
……
}> ${workDir}/gfs.0p25.${DateInfo}${TimeInfo}.f${stepStr}.log 2>&1 &
step=`expr $step + $interval`
sleep 2
这段意思是在后台做一些标准输出,但是java的ProcessBuilder是能获取这些输出的。如果没有read出来,就会堆积在缓存中,而最坑的是,这个缓存有一个固定大小,如果达到最大值,就会阻塞等待缓存被读出,而我的java代码里一直没有对其操作,所以等到输出的内容足够多,就一直阻塞,解决办法是,在java中增加如下代码:
ProcessBuilder pb = new ProcessBuilder(command);
pb.directory(new File(path));
int runningStatus = 0;
Process process;
try {
process = pb.start();
BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
BufferedReader staReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
new Thread(() -> {
String line;
try {
while ((line = staReader.readLine()) != null) {
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
String line;
try {
while ((line = errReader.readLine()) != null) {
if (line.startsWith("step=")) {
log.info("----开始下载:{}-----", line);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
runningStatus = process.waitFor();
errReader.close();
staReader.close();
} catch (InterruptedException e) {
log.error("error:" + e.getLocalizedMessage());
} catch (IOException e) {
throw new RuntimeException(e);
}
增加两个线程(也可以不使用线程)获取getErrorStream()
和getInputStream()
,把这两个inputStream中的数据读出来,就可以继续运行程序了。