目的:

java程序调用远程服务器的shell脚本来实现服务的起、停、重启。

前言(废话):

由于知道我经验不足,领导给新同事分配任务时说:“这个java远程调用shell脚本重启服务,他两可能搞不出来,你研究下吧。”被人鄙视了一把,于是自己搭虚拟机,查资料,试一把,突然发现,好多事情其实没那么难嘛。

正文:

Step1.准备脚本

重启的关键在于关闭,要关闭程序,那就要先找到程序pid,然后kill。
找到核心命令(获取pid):

1
ID=`ps -ef | grep "$NAME" | grep -v "grep" | grep -v kill | awk '{print $2}'`

于是初期博主调试后,写出了这样的shell

如果你的虚机是root用户,kill命令会很强大,所以一定判断参数是否为空或数字,否则,分分钟把你的所有进程都kill,虚机瞬间爆炸😂,重头再来。

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
#!/bin/sh
NAME=$1
##检查参数,不能为空或纯数字,否则会kill几乎所有进程,直接死机。非root用户的话,应该不会死机,会报permission denied.
a=`echo "$NAME" | grep [^0-9] >/dev/null && echo 0 || echo 1` #判断参数是否为数字,是数字则返回1,不是则返回0
if [ "$NAME" == "" ]; then #$NAME一定要加引号,不然$NAME为空的时候就成了if[ == "" ],会报错:unary operator
echo "未输入要kill的进程名"
elif [ $a = 1 ]; then
echo "进程名不能为纯数字"
else
##重启进程
echo "-----------------------"
echo -e "pNmae\t=\t$NAME"
ID=`ps -ef | grep "$NAME" | grep -v "grep" | grep -v "restart" | awk '{print $2}'`
echo -e "pid\t=\t$ID"
echo "-----------------------"
for id in $ID
do
kill -9 $id
echo "killed $id"
done
echo "-----------------------"
echo "restarted" $NAME
./$NAME 1>/dev/null 2>&1 &
#./$NAME
fi

然后领导交于博主一份专业的shell脚本,瞬间把博主的三脚猫脚本秒成渣了。于是博主认真研读,添加注释,并稍作修改得到以下脚本。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/bin/sh
# -*- coding: utf-8 -*-
#Filename: server.sh.Chant
#Author: Chant
#Email: statuschuan@gmail.com
#Date: 2017-06-03
#Desc:
#用途:该脚本用于停止、启动服务
#使用说明:
#启动该脚本时需要两个参数,参数均不能为空
#第一个参数为:程序入口名称
#第二个参数为:参数名称
#注意事项:
#远程调用时,请将脚本环境变量配置到/etc/bashrc或者用户目录下的.bashrc
print_usage()
{
echo "Usage: $0 COMMAND"
echo "where COMMAND is one of:"
echo " help Help print this usage message"
echo " start <server_name> <server_param> Start"
echo " stop <server_name> <server_param> Stop"
echo " restart <server_name> <server_param> Restart"
}
start(){
#echo "start not suport now."
cmd=$@
$cmd 1>/dev/null 2>&1 & #这里一定要写1>/dev/null 2>&1 不能只写&,否则远程调用时,会等待cmd的返回结果(stdOut),就么法愉快地玩耍了。
}
stop(){
#get arguments
SERVER_NAME=$1
SERVER_PARAM=$2
PROCESS_NAME="$1 $2"
#echo $PROCESS_NAME
if [ "$1" = "" ];
then
echo "第二个参数不能为空"
exit 0;
fi
if [ -z "$2" ];
then
echo "第三个参数不能为空"
exit 0;
fi
#get process's pids
pids=`ps -ef|grep "$PROCESS_NAME"|grep -v "grep"|awk '{print $2}'`
#Chant:使用以下命令可以过滤掉脚本本身的pid,就不用写后面的判断语句了。
#但是其实用$$获取当前脚本pid在逻辑上更严密,否则,万一你的脚本名和要操作的程序名有相同部分就会出问题,
#eg: 脚本名为:ser.sh 而程序名为:poser.sh,那么由于grep -v "$0" 就取不到其pid了
#pids=`ps -ef|grep "$PROCESS_NAME"|grep -v "grep"| grep -v "$0" |awk '{print $2}'`
# 为basename指定一个路径,basename命令会删掉所有的前缀包括最后一个slash(‘/’)字符,然后将字符串显示出来。
#pids=`ps -ef|grep "$PROCESS_NAME"|grep -v "grep"| grep -v "$(basename $0)" |awk '{print $2}'`
current_pid=$$
echo process pids is $pids.
#kill process
if [ -n "$pids" ]; #判断pids是否为空,引号必须加。"$pids" == ""等效
then
for pid in $pids
do
#current shell pid shoud not kill.
if [ $pid -ne $current_pid ];
then
#check $pid is exist or not
check=`ps -p $pid`
if [ $? -eq 0 ];
then
echo kill $pid start.
kill -9 $pid
#judge result
if [ $? -eq 0 ];
then
echo kill $pid success.
else
echo kill $pid fail.
fi
fi
fi
done
else
echo "$PROCESS_NAME does not exist."
fi
}
# get command arguments
COMMAND=$1
shift
# support help commands
case $COMMAND in
--help|-help|-h|help)
print_usage
exit 0
;;
"")
print_usage
exit 0
;;
"start")
start $@
echo "$@ started"
exit 0
;;
"stop")
stop $@
exit 0
;;
"restart")
start and stop的参数需要一致才可以,如果不一致则需要调整参数传入方式
stop $@
eep 3
tart $@
echo "$@ restarted"
exit 0
;;
esac

Step2.java程序远程调用shell

1.导入需要依赖的jar包。Java远程调用Shell脚本这个程序需要ganymed-ssh2-build210.jar包。里面还有example包,方便学习。为了调试方便,可以将\ganymed-ssh2-build210\src下的代码直接拷贝到我们的工程里,此源码的好处就是没有依赖很多其他的包,拷贝过来干干净净。
2.导入commons-io包,里面的IOUtils会经常使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.ethz.ganymed/ganymed-ssh2 -->
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>build210</version>
</dependency>
</dependencies>

3、编写RemoteShellExecutor工具类

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package RemoteShell;
import java.io.*;
import java.nio.charset.Charset;
import org.apache.commons.io.IOUtils;
import ch.ethz.ssh2.ChannelCondition;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;
/**
* Created by Chant on 2017/5/27.
* 远程调用脚本重启服务
*/
public class RemoteShellExecutor {
private Connection conn;
/** 远程机器IP */
private String ip;
/** 用户名 */
private String osUsername;
/** 密码 */
private String password;
private String charset = Charset.defaultCharset().toString();
private static final int TIME_OUT = 1000 * 5 * 60;
/**
* 构造函数
* @param ip
* @param usr
* @param pasword
*/
public RemoteShellExecutor(String ip, String usr, String pasword) {
this.ip = ip;
this.osUsername = usr;
this.password = pasword;
// System.out.println(charset);
}
/**
* 登录
* @return
* @throws IOException
*/
private boolean login() throws IOException {
conn = new Connection(ip);
conn.connect();
return conn.authenticateWithPassword(osUsername, password);
}
/**
* 执行脚本
*
* @param cmds
* @return
* @throws Exception
*/
public int exec(String cmds) throws Exception {
InputStream stdOut = null;
InputStream stdErr = null;
String outStr = "";
String outErr = "";
int ret = -1;
try {
if (login()) {
// Open a new {@link Session} on this connection
Session session = conn.openSession();
// Execute a command on the remote machine.
session.execCommand(cmds);
stdOut = new StreamGobbler(session.getStdout());
outStr = processStream(stdOut, charset);
stdErr = new StreamGobbler(session.getStderr());
outErr = processStream(stdErr, charset);
session.waitForCondition(ChannelCondition.EXIT_STATUS, TIME_OUT);
System.out.println("outStr=" +"\n"+ outStr);
System.out.println("outErr=" +"\n"+ outErr);
ret = session.getExitStatus();
} else {
throw new Exception("登录远程机器失败" + ip); // 自定义异常类 实现略
}
} finally {
if (conn != null) {
conn.close();
}
IOUtils.closeQuietly(stdOut);
IOUtils.closeQuietly(stdErr);
}
return ret;
}
/**
* @param in
* @param charset
* @return
* @throws IOException
* @throws UnsupportedEncodingException
*/
// private String processStream(InputStream in, String charset) throws Exception {
// byte[] buf = new byte[1024];
// StringBuilder sb = new StringBuilder();
// while (in.read(buf) != -1) {
// sb.append(new String(buf, charset));
// }
// return sb.toString();
// }
private String processStream(InputStream in, String charset)throws Exception {
StringBuilder sb = new StringBuilder();
BufferedReader bufr = new BufferedReader(new InputStreamReader(in,charset));
String line = null;
while((line = bufr.readLine()) != null){
sb.append(line);
sb.append("\n");//??换行符是依赖平台的
}
return sb.toString();
}
}

4、Java程序调用远程Shell

1
2
3
4
5
6
7
public static void main(String args[]) throws Exception {
RemoteShellExecutor("10.10.10.100", "root", "123123");
String proName = "entranceMain.sh";
String para = "1";
System.out.println(executor.exec("server.sh.Chant start " + proName +" "+ para));
}

将start换为restart,stop,测试结果如下,注意pid发生变化,说明重启成功。