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
2
String shell = "/bin/bash /Users/lc/dosomething.sh param1 param2";
Runtime.getRuntime().exec(shell);

如果需要得到脚本执行的输出结果:

1
2
3
4
5
6
Process process = Runtime.getRuntime().exec(shell);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}

脚本执行可能有问题,所以要输出错误信息:

1
2
3
4
BufferedReader readerErr = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while ((line = readerErr.readLine()) != null) {
System.out.println(line);
}

waitFor

可是在实际使用的时候发现有时候有结果输出,有时候却没有。
这就是第一个时好时坏的原因:主程序会等待Process执行一段时间,但是时间很短,可能Process还没有执行完就结束了。所有就会发生时候时候坏的情况,为了避免这种情况的发生,我们需要调用Process的waitFor方法:

1
2
3
4
5
6
7
Process process = Runtime.getRuntime().exec(shell);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();

该方法会让当前线程进入等待,直到Process中断或完成。waitFor方法还有一个int的返回值,0表示正常退出,其他值则表示执行异常。

close

重新打包部署以后以为都好了,但是过了一段时间再去使用的时候发现又没有结果输出了。问题出在哪呢?在StackOverFlow上找了好久才找到问题,也就是第二个时好时坏的原因:如果Process有返回,那么必须要关闭process.getInputStream(),否则waitFor会一直等待。所以我们加上对应流的close,最后再次重新打包部署。

1
2
3
4
5
Process process = Runtime.getRuntime().exec(shell);
// read InputStream and ErrorStream
reader.close();
readerErr.close();
process.waitFor();

exec != bash

执行shell脚本的时候好像没问题了,但是如果执行西面的命令时,就会发生错误。

1
2
String shell = "ls -a > all.txt";
Runtime.getRuntime().exec(shell);

错误信息:

1
2
> ls: >: No such file or directory<br/>
ls: all.txt: No such file or directory

原因是命令不会被再次解析,重定向符号>失效,这时候就需要用到exec的第二种重载方法:

1
2
String[] cmd = {"/bin/sh", "-c", "ls -a > all.txt"};
Process process = Runtime.getRuntime().exec(cmd);

ProcessBuilder

ProcessBuilder也可以用来执外部程序,它约等于Runtime.exec()方法的第一种重载方式,只是它的参数不能是String。而必须是String…或者ArrayList的形式。

1
2
3
4
5
6
7
8
9
10
ProcessBuilder pb = new ProcessBuilder("ls", "-a");
Process process = pb.redirectErrorStream(true).start();
InputStream stdin = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stdin));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close()
process.waitFor();

从代码种可以看出,它相比Runtime有一个好处,就是可以把ErrorStream重定向到stdin,这样我们就只需要读一个输入流就可以了,没有错误时,读出返回的正常结果,有错误时读出返回的异常信息。

More

更多关于Runtime.exec()可能发生的错误可以看下JavaWorld上的这篇文章When Runtime.exec() won’t