碰到2次这种需求了:需要给NCL做一个Java Web的套壳,前端参数传给后台,后台用命令行调用NCL出结果。
一般的做法
用Runtime类getRuntime().exec()或用p=processbuilder后p.start()
用jdk的Runtime类可以获得当前java程序的Runtime实例。每个java application只有一个runtime实例,显然这是一个单例模式。
getRuntime使用需要注意
参考经常被引用的这篇4个可能遇到的坑 以及中文版
- 用waitFor()方法等待执行完再调用结果,否则会抛出错误
- 子进程公用父进程的流,要使用bufferreader读取父进程的stream,防止满了之后导致死锁
- 不同平台命令兼容性(我这没这个问题)
- 对stream重定向或者pipeline不能通过shell命令(如2>&1),而是要编程手段解决。比如建立一个工具类新开thread来处理stdout和stderr
在NCL中可能遇到的问题和解决
- 脚本的environment和workdir参数,用processbuilder可以方便导入
- getRuntime和processbuilder传参的格式不同,getruntime接受整条命令,processbuilder接受的是分割的命令和参数。getruntime内部是分割后传给processbuilder
- NCL的输出在error,而且shell的重定向是不管用的。processbuilder有redirect方法,可以方便把stderror重定向
- NCL若最后没写quit或exit方法,执行完会一直hang住,用waitFor来控制NCL执行中出问题没退出而超时
- 在waitfor阻塞结束后才能调用bufferreader里面的值,如果想要实时看到执行的结果,需要新开thread,用streamgobbler工具类处理
processbuilder例子
不开thread用工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// 导入脚本的环境变量,执行command。重定向stderr,在waitfor阻塞结束之后一起返回结果
public String exeCmd(String command, Map<String, String> environment) {
try {
/* use processbuilder to easily redirect errorstream to inputstream */
ProcessBuilder pb =
new ProcessBuilder(command.split(" "));
pb.redirectErrorStream(true);
Map<String, String> env = pb.environment();
for (Map.Entry<String, String> entry : environment.entrySet()) {
env.put(entry.getKey(), entry.getValue());
}
Process p = pb.start();
BufferedReader buf = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder logString = new StringBuilder();
String line;
while ((line = buf.readLine()) != null) {
System.out.println(line);
logString.append(line);
logString.append("\n");
}
// wait for 1 minutes
boolean exitVal = p.waitFor(1, TimeUnit.MINUTES);
return (exitVal ? "Finished" : "Timeout") + " log: " + logString;
} catch (Exception e) {
//TODO: handle exception
return "failed " + e;
}
}
|
用streamgobbler工具类处理输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
// class定义略过
// process builder is expecting string[] and automatically add spaces
ProcessBuilder builder = new ProcessBuilder(cmd.split(" "));
builder.directory(new File(this.workingDir));
Process process = builder.start();
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream());
outputGobbler.start();
exitVal = process.waitFor(1, TimeUnit.MINUTES);
nclOutputFile = outputGobbler.getNclOutputFile();
System.out.println(exitVal ? "NCL Success" : "NCL Failed");
// streamgobbler类
// using nio is a little bit overkill
// use this stream gobbler to deal with output in a new thread
// or, when ncl encounters error and never quits, readline in main tread will be waiting forever
// only killing ncl process will continue the program
public class StreamGobbler extends Thread {
private InputStream is;
private String nclOutputFile = "";
public StreamGobbler(InputStream is) {
this.is = is;
}
public String getNclOutputFile() {
return nclOutputFile;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
if (line.contains("Plot Done")) {
nclOutputFile = line;
nclOutputFile = nclOutputFile.split("./")[1];
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
|