一. 将shell脚本放在resouces目录下,打成jar包,在linux上使用java -jar运行时,无法运行指定shell脚本。

这是因为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镜像中执行时,提示 error=2, No such file or directory。

在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调用。到此程序终于开始运行了。但还是有一些问题。

三. docker中shell的时间函数用不了了?

我的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")
四. java调用shell成功,但是执行到一半就莫名终止了。

关于这个问题,可能也比较有特殊性。经过一番检查在我的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中的数据读出来,就可以继续运行程序了。