在实际的生产项目中,往往涉及很多的人机交互,这个时候想要程序自主完成一些动作,就显得比较麻烦,比如输入登录密码或者和一些堡垒机做交互。下面讲一下如何应用expect4j来实现这些功能。
expect4j同样也是基于ssh协议的,他的底层依赖于jsch包,其实本质上他就是对jsch创建的通道流做处理,事先设定一个closure,当匹配到就做下一步操作,来达到自动交互完成一系类指令的功能。下面直接看代码,首先创建session
private static Session getSession(String usr,String ip,String pwd) throws JSchException {
Session session = null;
try {
JSch jsch = new JSch();
session = jsch.getSession(usr, ip, 22);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setPassword(pwd);
session.connect(3000);
} catch (JSchException e) {
if (session != null) {
session.disconnect();
session = null;
}
throw e;
}
return session;
}
然后利用session打开一个shell channel,然后将channel流提供给expect4j,创建一个expect客户端
ChannelShell channel = (ChannelShell) session.openChannel("shell");
Expect4j expect = new Expect4j(channel.getInputStream(), channel.getOutputStream());
channel.connect(3000);
下面就是重头戏了,怎么让expect知道我上一条指令是否执行完毕。这个时候我们需要定义一些终止符,来提示表示可以执行下一条指令。下面看代码
public static List getPattern(List promptRegEx, StringBuffer buffer)
throws MalformedPatternException {
List lstPattern = new ArrayList();
synchronized (promptRegEx) {
for (String regexElement : promptRegEx) {
RegExpMatch mat = new RegExpMatch(regexElement, x -> {
buffer.append(x.getBuffer());
// x.exp_continue();
});
lstPattern.add(mat);
}
}
lstPattern.add(new TimeoutMatch(DEFAULTTIMEOUT, x -> {
}));
return lstPattern;
}
上面函数中的promptRegEx即是自定义的终止符,一般就是操作系统的提示符,比如~或者$。非常重要的一点是,特殊字符必须进行转义,比如$,一定要写成\$,当然在JAVA中需要两个反斜杠来转义。buffer就是交互期间所有的操作以及结果记录,最后通过buffer.toString可以返回所有的结果。程序中我注释了一行 x.exp_continue(),这个表示即使匹配到了终止符,他也会继续往下逐个匹配,我个人觉得这个用处不大,而且非常影响性能,所以注释掉了,但是网上其他很多地方都加上了这句。
另外一点,如果指令执行期间遇到网络问题或者其他一些未知问题导致卡住怎么办,可以看到我在程序中额外定义了一个超时的closure即TimeoutMatch,这个超时时间可以自己设定。
下面就是具体的交互指令执行啦,样例程序如下
List lstPattern = getPattern(promptRegEx, buffer);
expect.expect(lstPattern);
expect.send(cmd1);
expect.expect(lstPattern);
expect.send(cmd2);
expect.expect(lstPattern);
expect函数就是寻找终止符的操作,他返回一个int结果,表示第几次找到匹配的结果,或者返回一个错误码。send函数就是发送指令进行执行了,一定要记得在指令后面加上换行符,表示执行,要不然不会执行的。
最后就是获取执行结果了,直接对上面的buffer进行toString就可以了。需要注意的是,如果远程机器返回结果有中文,且用的编码和当前主机环境变量使用编码不一致,这个时候就会出现乱码。解决办法就是,修改当前主机默认编码,如果实在不好修改,可以修改expect4j源码进行更改,他的源码中使用的编码就是获取的主机默认编码,其实可以手动自己设置。比如:
/** Creates a new instance of ReaderConsumer */
public StreamPair(InputStream is, OutputStream os) {
try {
this.is = new InputStreamReader(is, "GBK");
this.os = new OutputStreamWriter(os, "GBK");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}