Java执行外部程序的问题
通过java调用shell脚本,其实是创建一个本地进程。
Java.lang.Runtime.exec 方法和 Java.lang.ProcessBuilder.start 方法都可以创建一个本地的进程,然后返回代表这个进程的 Java.lang.Process 引用。
Runtime
java.lang.Runtime是一个本地运行环境类,其构造方法是私有的,只能通过getRuntime()来获取实例。Runtime的exec方法可以用来运行外部程序,如启动浏览器等。它有四种重载方式:
- public Process exec(String command);
- public Process exec(String[] cmdArray);
- public Process exec(String command, String[] envp);
- public Process exec(String[] cmdArray, String[] envp);
第三种就是第一种的基础上带上环境变量,同理,第四种是第二种带上环境变量。
如果我们要执行一个shell脚本,我们可以用第一种方法:
1 | String shell = "/bin/bash /Users/lc/dosomething.sh param1 param2"; |
如果需要得到脚本执行的输出结果:
1 | Process process = Runtime.getRuntime().exec(shell); |
脚本执行可能有问题,所以要输出错误信息:
1 | BufferedReader readerErr = new BufferedReader(new InputStreamReader(process.getErrorStream())); |
waitFor
可是在实际使用的时候发现有时候有结果输出,有时候却没有。
这就是第一个时好时坏的原因:主程序会等待Process执行一段时间,但是时间很短,可能Process还没有执行完就结束了。所有就会发生时候时候坏的情况,为了避免这种情况的发生,我们需要调用Process的waitFor方法:
1 | Process process = Runtime.getRuntime().exec(shell); |
该方法会让当前线程进入等待,直到Process中断或完成。waitFor方法还有一个int的返回值,0表示正常退出,其他值则表示执行异常。
close
重新打包部署以后以为都好了,但是过了一段时间再去使用的时候发现又没有结果输出了。问题出在哪呢?在StackOverFlow上找了好久才找到问题,也就是第二个时好时坏的原因:如果Process有返回,那么必须要关闭process.getInputStream(),否则waitFor会一直等待。所以我们加上对应流的close,最后再次重新打包部署。
1 | Process process = Runtime.getRuntime().exec(shell); |
exec != bash
执行shell脚本的时候好像没问题了,但是如果执行西面的命令时,就会发生错误。
1 | String shell = "ls -a > all.txt"; |
错误信息:
1 | ls: >: No such file or directory<br/> |
原因是命令不会被再次解析,重定向符号>
失效,这时候就需要用到exec的第二种重载方法:
1 | String[] cmd = {"/bin/sh", "-c", "ls -a > all.txt"}; |
ProcessBuilder
ProcessBuilder也可以用来执外部程序,它约等于Runtime.exec()方法的第一种重载方式,只是它的参数不能是String。而必须是String…或者ArrayList的形式。
1 | ProcessBuilder pb = new ProcessBuilder("ls", "-a"); |
从代码种可以看出,它相比Runtime有一个好处,就是可以把ErrorStream重定向到stdin,这样我们就只需要读一个输入流就可以了,没有错误时,读出返回的正常结果,有错误时读出返回的异常信息。
More
更多关于Runtime.exec()可能发生的错误可以看下JavaWorld上的这篇文章When Runtime.exec() won’t