分享知识 传递快乐

package com.xh.common.util.shell;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.List;
import java.util.Properties;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.SftpProgressMonitor;
import com.xh.common.util.constant.ConstantUtils;
import com.xh.common.util.date.DateFormatUtils;
import com.xh.common.util.file.FileUtils;
import com.xh.common.util.io.IOUtils;
import com.xh.common.util.log.LogUtils;
import com.xh.common.util.properties.PropertyUtils;

/**
* <b>Title: JSCH 远程Shell操作</b>
* <p>Description:
* shell管道本身就是交互模式的。要想停止,有两种方式: <br>
* 1).人为的发送一个exit命令,告诉程序本次交互结束 (本节使用的exit结束)<br>
* 2).使用字节流中的available方法,来获取数据的总大小,然后循环去读。为了避免阻塞。 <br>
* </p>
*
* @author H.Yang
* @email xhaimail@163.com
* @date 2019年8月16日
*/
public class RemoteJschUtils {

private static RemoteJschUtils instance = null;

private static JSch jsch = null;
private static Session session = null;
private static Channel channel = null;

// 让构造函数为 private,这样该类就不会被实例化
private RemoteJschUtils() {
}

/**
* <b>Title: 单例</b>
* <p>Description: 双重检查</p>
*
* @author H.Yang
*
* @return
*/
public static RemoteJschUtils newRemoteJsch() {
if (instance == null) {
synchronized (RemoteJschUtils.class) {
// 双重检查
if (instance == null) {
instance = new RemoteJschUtils();
}
}
}
return instance;
}

/**
* <b>Title: 获取Session</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @return
*/
public Session getSession() {
if (session == null || !session.isConnected()) {
session = this.connect(PropertyUtils.get("shell.host"), PropertyUtils.get("shell.userName"), PropertyUtils.get("shell.password"));
}
return session;
}

/**
* <b>Title: 获取Channel连接</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param type
* @param isConnect 是否开启连接,true开启
* @return
*/
public Channel getChannel(String type, boolean isConnect) {
try {
if (channel == null || !channel.isConnected()) {
channel = this.getSession().openChannel(type);
if (isConnect) {
channel.connect(PropertyUtils.getInt("shell.connectTimeout", 0));
}
}
} catch (JSchException e) {
e.printStackTrace();
}
return channel;
}

/**
* <b>Title: 创建连接</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param host
* @param username
* @param password
* @return
*/
public synchronized Session connect(String host, String username, String password) {
if (jsch == null) {
jsch = new JSch();
}
if (session == null) {
LogUtils.info(this.getClass(), "Shell 初始化配置...");
int port = PropertyUtils.getInt("shell.port", 22);
try {
// 判断端口
if (port <= 0) {
session = jsch.getSession(username, host);// 连接服务器,采用默认端口
} else {
session = jsch.getSession(username, host, port);// 采用指定的端口连接服务器
}
// 如果服务器连接不上,则抛出异常
if (session == null) {
throw new RuntimeException("连接为空");
}
session.setPassword(password);
// 设置第一次登陆的时候提示,可选值:(ask | yes | no)
session.setConfig("StrictHostKeyChecking", "no");// 跳过主机密钥检查
// 设置登陆超时时间
session.connect(PropertyUtils.getInt("shell.connectTimeout", 0));
} catch (JSchException e) {
e.printStackTrace();
}
}
return session;
}

/**
* <b>Title: 释放资源</b>
* <p>Description: </p>
*
* @author H.Yang
*
*/
public void releaseResource() {
if (session != null) {
session.disconnect();
session = null;
}
}

/**
* <b>Title: 执行Shell命令并返回执行结果信息</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param commands
* @return
*/
public String shell(String... commands) {
ChannelShell channelShell = null;
try {
// 执行多行
channelShell = (ChannelShell) this.getChannel("shell", false);
channelShell.setPtyType("dumb");
channelShell.connect();

this.sendCommands(channelShell, commands);

String msg = IOUtils.toString(channelShell.getInputStream(), PropertyUtils.get("shell.charset", ConstantUtils.CHARSET_UTF8));
LogUtils.info(this.getClass(), msg);

// 保存日志
this.asynLog(msg);

return msg;
} catch (Exception e) {
e.printStackTrace();
} finally {
channelShell.disconnect();
}
return null;
}

public List<String> shells(String... commands) {
ChannelShell channelShell = null;
try {
// 执行多行
channelShell = (ChannelShell) this.getChannel("shell", false);
channelShell.setPtyType("dumb");
channelShell.connect();

this.sendCommands(channelShell, commands);

List<String> list = (List<String>) RemoteShellUtils.processStdout(channelShell.getInputStream(), PropertyUtils.get("shell.charset", ConstantUtils.CHARSET_UTF8), true);

StringBuffer buffer = new StringBuffer();
for (String str : list) {
buffer.append(str + "\n");
}
// 保存日志
this.asynLog(buffer.toString());

return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
channelShell.disconnect();
}
return null;
}

/**
* <b>Title: 执行Shell命令并返回执行结果信息</b>
* <p>Description: 一次只能执行一条</p>
*
* @author H.Yang
*
* @param command
* @return
*/
public String exec(String command) {
ChannelExec channelExec = null;
try {
// 默认方式,执行单句命令
channelExec = (ChannelExec) this.getChannel("exec", false);
channelExec.setCommand(command);
channelExec.setErrStream(System.err);
channelExec.connect();

// 读取返回信息
String msg = IOUtils.toString(channelExec.getInputStream(), PropertyUtils.get("shell.charset", ConstantUtils.CHARSET_UTF8));
LogUtils.info(this.getClass(), msg);

// 保存日志
this.asynLog(msg);
return msg;
} catch (Exception e) {
e.printStackTrace();
} finally {
channelExec.disconnect();
}
return null;
}

public List<String> execs(String command) {
ChannelExec channelExec = null;
try {
// 默认方式,执行单句命令
channelExec = (ChannelExec) this.getChannel("exec", false);
channelExec.setCommand(command);
channelExec.setErrStream(System.err);
channelExec.connect();

// 读取返回信息
List<String> list = (List<String>) RemoteShellUtils.processStdout(channelExec.getInputStream(), PropertyUtils.get("shell.charset", ConstantUtils.CHARSET_UTF8), true);

StringBuffer buffer = new StringBuffer();
for (String str : list) {
buffer.append(str + "\n");
}
// 保存日志
this.asynLog(buffer.toString());

return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
channelExec.disconnect();
}
return null;
}

/**
* <b>Title: 上传文件</b>
* <p>Description: 采用默认的传输模式:OVERWRITE</p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @return
*/
public boolean push(String src, String targetDir) {
// 推送采用默认的覆盖式推送
return this.push(src, targetDir, ChannelSftp.OVERWRITE);
}

/**
* <b>Title: 上传文件</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @param mode 传输模式:
* <pre>ChannelSftp.OVERWRITE:0</pre>
* <pre>ChannelSftp.RESUME:1</pre>
* <pre>ChannelSftp.APPEND:2</pre>
*
* <p>ChannelSftp.OVERWRITE:完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。</p>
* <p>ChannelSftp.RESUME:恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。</p>
* <p>ChannelSftp.APPEND:追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。</p>
* @return
*/
public boolean push(String src, String targetDir, int mode) {

return push(src, targetDir, null, mode);
}

/**
* <b>Title: 上传文件</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @param mode 传输模式:
* <pre>ChannelSftp.OVERWRITE:0</pre>
* <pre>ChannelSftp.RESUME:1</pre>
* <pre>ChannelSftp.APPEND:2</pre>
*
* <p>ChannelSftp.OVERWRITE:完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。</p>
* <p>ChannelSftp.RESUME:恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。</p>
* <p>ChannelSftp.APPEND:追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。</p>
* @return
*/
public boolean push(File src, String targetDir, int mode) {

return push(src, targetDir, null, mode);
}

/**
* <b>Title: 上传文件</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @param monitor 实现了SftpProgressMonitor接口的monitor对象来监控传输的进度
* @param mode 传输模式:
* <pre>ChannelSftp.OVERWRITE:0</pre>
* <pre>ChannelSftp.RESUME:1</pre>
* <pre>ChannelSftp.APPEND:2</pre>
*
* <p>ChannelSftp.OVERWRITE:完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。</p>
* <p>ChannelSftp.RESUME:恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。</p>
* <p>ChannelSftp.APPEND:追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。</p>
* @return
*/
public boolean push(String src, String targetDir, SftpProgressMonitor monitor, int mode) {
ChannelSftp channelSftp = null;
try {
channelSftp = (ChannelSftp) this.getChannel("sftp", true);
// 检查目录是否存在
this.targetDirExists(channelSftp, targetDir);
// 推送文件
channelSftp.put(src, targetDir, monitor, mode);
return true;
} catch (SftpException e) {
e.printStackTrace();
} finally {
channelSftp.quit();
channelSftp.disconnect();
}
return false;
}

/**
* <b>Title: 上传文件</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @param monitor 实现了SftpProgressMonitor接口的monitor对象来监控传输的进度
* @param mode 传输模式:
* <pre>ChannelSftp.OVERWRITE:0</pre>
* <pre>ChannelSftp.RESUME:1</pre>
* <pre>ChannelSftp.APPEND:2</pre>
*
* <p>ChannelSftp.OVERWRITE:完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。</p>
* <p>ChannelSftp.RESUME:恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。</p>
* <p>ChannelSftp.APPEND:追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。</p>
* @return
*/
public boolean push(File src, String targetDir, SftpProgressMonitor monitor, int mode) {
ChannelSftp channelSftp = null;
try {
channelSftp = (ChannelSftp) this.getChannel("sftp", true);
// 检查目录是否存在
this.targetDirExists(channelSftp, targetDir);
// 推送文件
channelSftp.put(new FileInputStream(src), targetDir, monitor, mode);
return true;
} catch (SftpException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
channelSftp.quit();
channelSftp.disconnect();
}
return false;
}

/**
* <b>Title: 下载</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @return
*/
public boolean pull(String src, String targetDir) {

return this.pull(src, targetDir, null, ChannelSftp.OVERWRITE);
}

/**
* <b>Title: 下载</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @param mode 传输模式:
* <pre>ChannelSftp.OVERWRITE:0</pre>
* <pre>ChannelSftp.RESUME:1</pre>
* <pre>ChannelSftp.APPEND:2</pre>
*
* <p>ChannelSftp.OVERWRITE:完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。</p>
* <p>ChannelSftp.RESUME:恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。</p>
* <p>ChannelSftp.APPEND:追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。</p>
* @return
*/
public boolean pull(String src, String targetDir, int mode) {

return this.pull(src, targetDir, null, mode);
}

/**
* <b>Title: 下载</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @param monitor 实现了SftpProgressMonitor接口的monitor对象来监控传输的进度
* @return
*/
public boolean pull(String src, String targetDir, SftpProgressMonitor monitor) {

return this.pull(src, targetDir, monitor, ChannelSftp.OVERWRITE);
}

/**
* <b>Title: 下载</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @param monitor 实现了SftpProgressMonitor接口的monitor对象来监控传输的进度
* @param mode 传输模式:
* <pre>ChannelSftp.OVERWRITE:0</pre>
* <pre>ChannelSftp.RESUME:1</pre>
* <pre>ChannelSftp.APPEND:2</pre>
*
* <p>ChannelSftp.OVERWRITE:完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。</p>
* <p>ChannelSftp.RESUME:恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。</p>
* <p>ChannelSftp.APPEND:追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。</p>
* @return
*/
public boolean pull(String src, String targetDir, SftpProgressMonitor monitor, int mode) {
ChannelSftp channelSftp = null;
try {
channelSftp = (ChannelSftp) this.getChannel("sftp", true);
FileUtils.makeDir(targetDir);
// 推送文件
channelSftp.get(src, targetDir, monitor, mode);
return true;
} catch (SftpException e) {
e.printStackTrace();
} finally {
channelSftp.quit();
channelSftp.disconnect();
}
return false;
}

/**
* <b>Title: 下载</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @return
*/
public boolean pull(String src, File targetDir) {

return this.pull(src, targetDir, null);
}

/**
* <b>Title: 下载</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param src 源文件
* @param targetDir 目标目录
* @param monitor 实现了SftpProgressMonitor接口的monitor对象来监控传输的进度
* @return
*/
public boolean pull(String src, File targetDir, SftpProgressMonitor monitor) {
ChannelSftp channelSftp = null;
try {
channelSftp = (ChannelSftp) this.getChannel("sftp", true);
if (!targetDir.exists()) {
if (targetDir.mkdirs()) {
LogUtils.info(FileUtils.class, "创建目录" + targetDir.getPath() + "成功!");
}
}
// 推送文件
channelSftp.get(src, new FileOutputStream(targetDir), monitor);
return true;
} catch (SftpException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
channelSftp.quit();
channelSftp.disconnect();
}
return false;
}

/**
* <b>Title: 删除文件</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param dir 源目录
* @param deleteFile 删除文件
* @return
*/
public boolean delete(String dir, String deleteFile) {
ChannelSftp channelSftp = null;
try {
channelSftp = (ChannelSftp) this.getChannel("sftp", true);
channelSftp.cd(dir); // 进入的目录应该是要删除的目录的上一级
channelSftp.rm(deleteFile);// 删除文件
return true;
} catch (SftpException e) {
e.printStackTrace();
} finally {
channelSftp.quit();
channelSftp.disconnect();
}
return false;
}

public boolean deleteDir(String dir, String deleteDir) {
ChannelSftp channelSftp = null;
try {
channelSftp = (ChannelSftp) this.getChannel("sftp", true);
channelSftp.cd(dir); // 进入的目录应该是要删除的目录的上一级
channelSftp.rmdir(deleteDir);// 删除目录
return true;
} catch (SftpException e) {
e.printStackTrace();
} finally {
channelSftp.quit();
channelSftp.disconnect();
}
return false;
}

/**
* <b>Title: 返回目录下所有文件名称</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param dir
* @return
*/
public List<String> get(String dir) {
ChannelSftp channelSftp = null;
try {
channelSftp = (ChannelSftp) this.getChannel("sftp", true);
return channelSftp.ls(dir); // 返回目录下所有文件名称
} catch (SftpException e) {
e.printStackTrace();
} finally {
channelSftp.quit();
channelSftp.disconnect();
}
return null;
}

/**
* <b>Title: 重命名</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param dir 目录
* @param oldName 要修改的文件名
* @param newName 新文件名
* @return
*/
public boolean rename(String dir, String oldName, String newName) {
ChannelSftp channelSftp = null;
try {
channelSftp = (ChannelSftp) this.getChannel("sftp", true);
channelSftp.cd(dir); // 进入的目录应该是要删除的目录的上一级
channelSftp.rename(oldName, newName);
return true;
} catch (SftpException e) {
e.printStackTrace();
} finally {
channelSftp.quit();
channelSftp.disconnect();
}
return false;
}

/**
* <b>Title: 判断目录是否存在,如果不存在则创建,并切换到创建的目录</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param channelSftp
* @param targetDir
* @return
* @throws SftpException
*/
private Channel targetDirExists(ChannelSftp channelSftp, String targetDir) throws SftpException {
try {
channelSftp.cd(targetDir);
} catch (SftpException e) {
if (ChannelSftp.SSH_FX_NO_SUCH_FILE == e.id) { // 指定上传路径不存在
channelSftp.mkdir(targetDir);// 创建目录
channelSftp.cd(targetDir); // 进入目录
}
}
return channelSftp;
}

/**
* <b>Title: 发送Shell命令</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param channel
* @param commands
*/
private void sendCommands(Channel channel, String... commands) {
try {
PrintStream out = new PrintStream(channel.getOutputStream());
for (String command : commands) {
out.println(command);
}
out.println("exit");// 加上个就是为了,结束本次交互
out.flush();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* <b>Title: 异步保存日志</b>
* <p>Description: </p>
*
* @author H.Yang
*
* @param msg
*/
private void asynLog(String msg) {
// 保存日志
new Thread(new Runnable() {
@Override
public void run() {
if (PropertyUtils.getBoolean("shell.logFileDir", false)) {
String path = PropertyUtils.get("shell.logFileDir") + DateFormatUtils.getCurrentTime(ConstantUtils.DATA_FORMATTER0_1) + "/";
FileUtils.makeDir(path);
path += PropertyUtils.get("shell.logFileName", "shell_log.log");
IOUtils.writeFile(path, msg, ConstantUtils.CHARSET_UTF8);
}
}
}).start();
}

public static void main(String[] args) throws IOException {
Properties props = new Properties();
props.load(RemoteJschUtils.class.getClassLoader().getResourceAsStream("log4j.properties"));

PropertyUtils property = PropertyUtils.newProperty();
String fileName = "application-files.properties";
property.init(fileName);

RemoteJschUtils shell = RemoteJschUtils.newRemoteJsch();
// =>连接访问
// shell.shell("cd /home/guest/temp/", "ll");
// List<String> list = shell.shells("cd /home/guest/temp/", "ll");
// int i = 0;
// for (String str : list) {
// System.out.println(i + " == " + str);
// i++;
// }

// shell.exec("ls -l");

// => 上传
// String src = "E:/TEMP/wisdomChannel.mp4";
// String dir = "/home/guest/temp/";
// shell.push(src, dir);
// shell.push(src, dir, new DetailsProgressMonitor(), ChannelSftp.OVERWRITE);
// shell.push(src, dir, new FileProgressMonitor(), ChannelSftp.OVERWRITE);

// => 下载
// String src = "/home/guest/temp/wisdomChannel.mp4";
// String targetDir = "E:/TEMP/190822/wisdomChannel.mp4";
// shell.pull(src, targetDir, new DetailsProgressMonitor());

// =>
// String dir = "/home/guest/";
// String deleteFile = "/home/guest/temp/wisdomChannel.mp4";
// shell.delete(dir, deleteFile);
// shell.deleteDir(dir, deleteFile);

String hosts = "10.1.1.101,guest,1234567;10.1.1.101,guest,1234567;10.1.1.101,guest,1234567;";
String[] servers = hosts.split(";"), //
host = null;
for (String server : servers) {
host = server.split(",");
shell.connect(host[0], host[1], host[2]);

String cmd = "df -k | grep -v Filesystem| awk '{print int($5)}'";
// String result = shell.shell(cmd);
// String[] arr = result.split("\n");
// for (String space : arr) {
// System.out.println("====" + space);
// }

List<String> list = shell.shells(cmd);
for (String space : list) {
System.out.println("====" + space);
}
shell.releaseResource();
}

}

}

————————————
如有不妥之处请留言指正。
相互学习,共同进步。