需求:
项目运营过程中不可避免的需要监控服务器指标,这时,使用 Java 代码远程调用 shell 脚本来获取服务器的一些信息,不失为一种常见的解决方案。
实现思路
使用 Java 代码模拟登录服务器,然后使用 shell 命令操作服务器。
必要说明
- 本 demo 为 SpringBoot 项目,JDK为1.8.0,IDE为IDEA2020.1。
- Java 代码调用的 shell 脚本已提前放置在服务器的相应位置。
- 若在使用 Java 代码调用 shell 脚本时,未传递服务器相关信息(
SSHVo),则默认使用配置文件ssh.properties中相应的参数操作服务器。
话不多说,直接上代码。
代码
代码结构

pom.xml
<!-- Java远程调用shell脚本 --><dependency><groupId>ch.ethz.ganymed</groupId><artifactId>ganymed-ssh2</artifactId><version>262</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency>
SSHVo 实体类
package com.demo.entity;/*** @Author: 瞎琢磨先生lyg* @Date: 2020/4/4 12:10* @Version 1.0* Just do it, Just do IT*/public class SSHVo {private String ipAddr;private Integer port;private String userName;private String password;private String cmds;public SSHVo() {}public SSHVo(String ipAddr, Integer port, String userName, String password, String cmds) {this.ipAddr = ipAddr;this.port = port;this.userName = userName;this.password = password;this.cmds = cmds;}public String getIpAddr() {return ipAddr;}public void setIpAddr(String ipAddr) {this.ipAddr = ipAddr;}public Integer getPort() {return port;}public void setPort(Integer port) {this.port = port;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getCmds() {return cmds;}public void setCmds(String cmds) {this.cmds = cmds;}@Overridepublic String toString() {final StringBuilder sb = new StringBuilder("{");sb.append("\"ipAddr\":\"").append(ipAddr).append('\"');sb.append(",\"port\":").append(port);sb.append(",\"userName\":\"").append(userName).append('\"');sb.append(",\"password\":\"").append(password).append('\"');sb.append(",\"cmds\":\"").append(cmds).append('\"');sb.append('}');return sb.toString();}}
SSH 工具类
SSH 连接、登录、操作服务器工具类
package com.demo.tool;import ch.ethz.ssh2.Connection;import ch.ethz.ssh2.Session;import com.demo.entity.SSHVo;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.PropertySource;import org.springframework.stereotype.Component;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.nio.charset.Charset;import java.nio.charset.StandardCharsets;/*** @Author: 瞎琢磨先生lyg* @Date: 2020/4/4 12:10* @Version 1.0* Just do it, Just do IT*/@Component@PropertySource(value = {"classpath:config/ssh.properties"})public class SSHTool {private static Charset charset = StandardCharsets.UTF_8;private static Connection conn;private static String ipAddr;private static Integer port;private static String userName;private static String password;private static String cmds;@Value("${ipAddr}")public void setIpAddr(String ipAddr) {SSHTool.ipAddr = ipAddr;}@Value("${port}")public void setPort(Integer port) {SSHTool.port = port;}@Value("${userName}")public void setUserName(String userName) {SSHTool.userName = userName;}@Value("${password}")public void setPassword(String password) {SSHTool.password = password;}@Value("${cmds}")public void setCmds(String cmds) {SSHTool.cmds = cmds;}/*** 登录远程Linux主机** @return 是否登录成功*/private static boolean login(SSHVo sshVo) {try {// 连接//conn = new Connection(ipAddr, port);conn = new Connection(sshVo.getIpAddr(), sshVo.getPort());conn.connect();// 认证return conn.authenticateWithPassword("root", sshVo.getPassword());} catch (IOException e) {e.printStackTrace();return false;}}/*** 执行Shell脚本或命令** @param sshVo .cmds 命令行序列* @return 脚本输出结果*/public static StringBuilder exec(SSHVo sshVo) throws IOException {InputStream in = null;StringBuilder result = new StringBuilder();if (sshVo == null || (sshVo != null && (sshVo.getIpAddr() == null || "".equals(sshVo.getIpAddr())))) {sshVo = new SSHVo(ipAddr, port, userName, password, cmds);}System.out.println(sshVo);try {if (SSHTool.login(sshVo)) {// 打开一个会话Session session = conn.openSession();session.execCommand(sshVo.getCmds());in = session.getStdout();result = SSHTool.processStdout(in, SSHTool.charset);conn.close();}} finally {if (null != in) {in.close();}}return result;}/*** 解析流获取字符串信息** @param in 输入流对象* @param charset 字符集* @return 脚本输出结果*/public static StringBuilder processStdout(InputStream in, Charset charset) throws FileNotFoundException {byte[] buf = new byte[1024];StringBuilder sb = new StringBuilder();// OutputStream os = new FileOutputStream("./data.txt");try {int length;while ((length = in.read(buf)) != -1) {// os.write(buf, 0, c);sb.append(new String(buf, 0, length));}} catch (IOException e) {e.printStackTrace();}return sb;}}
ssh.properties
(注意位置是在 resources/config 目录下)
ipAddr=124.160.108.89port=22userName=rootpassword=rootcmds=sh -x /test/shell.sh
测试类
public static void main(String[] args) throws IOException {SSHTool.exec(new SSHVo("124.160.108.88",22,"root","root","mkdir -p /java/shell"));}
访问 localhost:8080/shell

查看服务器,发现服务器上也调用了相应的 shell 脚本,说明 Java 代码调用 shell 脚本执行成功。
[注意事项]
- 使用Java代码调用shell脚本杀死当前服务,然后重启。无论尝试多少次,都无法进行重启。而单纯的使用shell实现杀死并重启服务却正常。
原因:Java代码调用shell杀死当前服务后,相当于调用shell脚本的主线程被杀了,子线程调用也就无从说起了。
解决办法:(曲线救国)。使用守护线程启动服务,当Java代码调用shell脚本杀死服务后,守护线程检测到服务被杀死,会自动将服务重启。
- 在win系统下编写的shell脚本极有可能无法在linux服务器上正常运行。
原因:就在于编码格式不一致。
解决办法:要么就在 linux 服务器上编写脚本,要么在win电脑上编写完毕后,使用 notepad++ 将其转码成linux的编码格式。
- 使用 shell 脚本调用 MySQL 脚本(eg. user.sql)报错,但是单独将其放在 Navicat 中执行,却一点错误都没有。
原因:SQL 脚本中存在以”#”开头的注释,导致 linux 上无法正常解析。
解决办法:将 SQL 脚本中所有以”#”开头的注释统统替换成”— “的注释(注意:—和待注释的内容之间要用”空格”隔开)。
