有时候我们需要调用系统命令执行一些东西,可能是为了方便,也可能是没有办法必须要调用。涉及执行系统命令的东西,则就不能做跨平台了,这和java语言的初衷是相背的。
废话不多说,java如何执行shell命令?自然是调用java语言类库提供的接口API了。
1. java执行shell的api
执行shell命令,可以说系统级的调用,编程语言自然必定会提供相应api操作了。在java中,有两个api供调用:Runtime.exec(), Process API. 简单使用如下:
1.1. Runtime.exec() 实现
调用实现如下:
简单的说就是只有一行调用即可:Runtime.getRuntime().exec("cmd.exe /c dir") ; 看起来非常简洁。
1.2. ProcessBuilder 实现
使用ProcessBuilder需要自己操作更多东西,也因此可以自主设置更多东西。(但实际上底层与Runtime是一样的了),用例如下:
看起来是要麻烦些,但实际上是差不多的,只是上一个用例没有处理输出日志而已。但总体来说的 ProcessBuilder 的可控性更强,所以一般使用这个会更自由些。
以下Runtime.exec()的实现:
2. 调用shell思考事项
从上面来看,要调用系统命令,并非难事。那是否就意味着我们可以随便调用现成方案进行处理工作呢?当然不是,我们应当要考虑几个问题?
1. 调用系统命令是进程级别的调用;
进程与线程的差别大家懂的,更加重量级,开销更大。在java中,我们更多的是使用多线程进行并发。但如果用于系统调用,那就是进程级并发了,而且外部进程不再受jvm控制,出了问题也就不好玩了。所以,不要随便调用系统命令是个不错的实践。
2. 调用系统命令是硬件相关的调用;
java语言的思想是一次编写,到处使用。但如果你使用的系统调用,则不好处理了,因为每个系统支持的命令并非完全一样的,你的代码也就会因环境的不一样而表现不一致了。健壮性就下来了,所以,少用为好。
3. 内存是否够用?
一般我们jvm作为一个独立进程运行,会被分配足够多的内存,以保证运行的顺畅与高效。这时,可能留给系统的空间就不会太多了,而此时再调用系统进程运行业务,则得提前预估下咯。
4. 进程何时停止?
当我调起一个系统进程之后,我们后续如何操作?比如是异步调用的话,可能就忽略掉结果了。而如果是同步调用的话,则当前线程必须等待进程退出,这样会让我们的业务大大简单化了。因为异步需要考虑的事情往往很多。
5. 如何获取进程日志信息?
一个shell进程的调用,可能是一个比较耗时的操作,此时应该是只要任何进度,就应该汇报出来,从而避免外部看起来一直没有响应,从而无法判定是死掉了还是在运行中。而外部进程的通信,又不像一个普通io的调用,直接输出结果信息。这往往需要我们通过两个输出流进行捕获。而如何读取这两个输出流数据,就成了我们获取日志信息的关键了。ProcessBuilder 是使用inputStream 和 errStream 来表示两个输出流, 分别对应操作系统的标准输出流和错误输出流。但这两个流都是阻塞io流,如果处理不当,则会引起系统假死的风险。
6. 进程的异常如何捕获?
在jvm线程里产生的异常,可以很方便的直接使用try...catch... 捕获,而shell调用的异常呢?它实际上并不能直接抛出异常,我们可以通过进程的返回码来判定是否发生了异常,这些错误码一般会遵循操作系统的错误定义规范,但时如果是我们自己写的shell或者其他同学写的shell就无法保证了。所以,往往除了我们要捕获错误之外,至少要规定0为正确的返回码。其他错误码也尽量不要乱用。其次,我们还应该在发生错误时,能从错误输出流信息中,获取到些许的蛛丝马迹,以便我们可以快速排错。
以上问题,如果都能处理得当,那么我认为,这个调用就是安全的。反之则是有风险的。
不过,问题看着虽然多,但都是些细化的东西,也无需太在意。基本上,我们通过线程池来控制进程的膨胀问题;通过读取io流来解决异常信息问题;通过调用类型规划内存及用量问题;
3. 完整的shell调用参考
说了这么多理论,还不如来点实际。don't bb, show me the code!
以上实现,完成了我们在第2点中讨论的几个问题:
1. 主要使用 ProcessBuilder 完成了shell的调用;
2. 支持读取进程的所有输出信息,且在必要的时候,支持使用单独的文件进行接收输出日志;
3. 在进程执行异常时,支持抛出对应异常,且给出一定的errMessage描述;
4. 如果想控制调用进程的数量,则在外部调用时控制即可;
5. 使用两个线程接收两个输出流,避免出现应用假死,使用newCachedThreadPool线程池避免过快创建线程;
接下来,我们进行下单元测试:
至此,我们的一个安全可靠的shell运行功能就搞定了。