答案 0 :(得分:90)
以下用Java编写的代码示例将允许您在Java程序中通过SSH在外部计算机上执行任何命令。您需要包含com.jcraft.jsch jar文件。
/*
* SSHManager
*
* @author cabbott
* @version 1.0
*/
package cabbott.net;
import com.jcraft.jsch.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SSHManager
{
private static final Logger LOGGER =
Logger.getLogger(SSHManager.class.getName());
private JSch jschSSHChannel;
private String strUserName;
private String strConnectionIP;
private int intConnectionPort;
private String strPassword;
private Session sesConnection;
private int intTimeOut;
private void doCommonConstructorActions(String userName,
String password, String connectionIP, String knownHostsFileName)
{
jschSSHChannel = new JSch();
try
{
jschSSHChannel.setKnownHosts(knownHostsFileName);
}
catch(JSchException jschX)
{
logError(jschX.getMessage());
}
strUserName = userName;
strPassword = password;
strConnectionIP = connectionIP;
}
public SSHManager(String userName, String password,
String connectionIP, String knownHostsFileName)
{
doCommonConstructorActions(userName, password,
connectionIP, knownHostsFileName);
intConnectionPort = 22;
intTimeOut = 60000;
}
public SSHManager(String userName, String password, String connectionIP,
String knownHostsFileName, int connectionPort)
{
doCommonConstructorActions(userName, password, connectionIP,
knownHostsFileName);
intConnectionPort = connectionPort;
intTimeOut = 60000;
}
public SSHManager(String userName, String password, String connectionIP,
String knownHostsFileName, int connectionPort, int timeOutMilliseconds)
{
doCommonConstructorActions(userName, password, connectionIP,
knownHostsFileName);
intConnectionPort = connectionPort;
intTimeOut = timeOutMilliseconds;
}
public String connect()
{
String errorMessage = null;
try
{
sesConnection = jschSSHChannel.getSession(strUserName,
strConnectionIP, intConnectionPort);
sesConnection.setPassword(strPassword);
// UNCOMMENT THIS FOR TESTING PURPOSES, BUT DO NOT USE IN PRODUCTION
// sesConnection.setConfig("StrictHostKeyChecking", "no");
sesConnection.connect(intTimeOut);
}
catch(JSchException jschX)
{
errorMessage = jschX.getMessage();
}
return errorMessage;
}
private String logError(String errorMessage)
{
if(errorMessage != null)
{
LOGGER.log(Level.SEVERE, "{0}:{1} - {2}",
new Object[]{strConnectionIP, intConnectionPort, errorMessage});
}
return errorMessage;
}
private String logWarning(String warnMessage)
{
if(warnMessage != null)
{
LOGGER.log(Level.WARNING, "{0}:{1} - {2}",
new Object[]{strConnectionIP, intConnectionPort, warnMessage});
}
return warnMessage;
}
public String sendCommand(String command)
{
StringBuilder outputBuffer = new StringBuilder();
try
{
Channel channel = sesConnection.openChannel("exec");
((ChannelExec)channel).setCommand(command);
InputStream commandOutput = channel.getInputStream();
channel.connect();
int readByte = commandOutput.read();
while(readByte != 0xffffffff)
{
outputBuffer.append((char)readByte);
readByte = commandOutput.read();
}
channel.disconnect();
}
catch(IOException ioX)
{
logWarning(ioX.getMessage());
return null;
}
catch(JSchException jschX)
{
logWarning(jschX.getMessage());
return null;
}
return outputBuffer.toString();
}
public void close()
{
sesConnection.disconnect();
}
}
进行测试。
/**
* Test of sendCommand method, of class SSHManager.
*/
@Test
public void testSendCommand()
{
System.out.println("sendCommand");
/**
* YOU MUST CHANGE THE FOLLOWING
* FILE_NAME: A FILE IN THE DIRECTORY
* USER: LOGIN USER NAME
* PASSWORD: PASSWORD FOR THAT USER
* HOST: IP ADDRESS OF THE SSH SERVER
**/
String command = "ls FILE_NAME";
String userName = "USER";
String password = "PASSWORD";
String connectionIP = "HOST";
SSHManager instance = new SSHManager(userName, password, connectionIP, "");
String errorMessage = instance.connect();
if(errorMessage != null)
{
System.out.println(errorMessage);
fail();
}
String expResult = "FILE_NAME\n";
// call sendCommand for each command and the output
//(without prompts) is returned
String result = instance.sendCommand(command);
// close only after all commands are sent
instance.close();
assertEquals(expResult, result);
}
答案 1 :(得分:29)
这是一个无耻的插件,但我现在只是writing一些广泛的Javadoc for JSch。
此外,JSch Wiki中现在有Manual(主要由我编写)。
关于原始问题,没有一个处理流的例子。 一如既往地读取/写入流。
但是,通过读取shell的输出(这与SSH协议无关),简直无法确定shell中的一个命令何时完成(
)。如果shell是交互式的,即它有一个连接的终端,它通常会打印一个你可以尝试识别的提示。但至少从理论上讲,这个提示字符串也可能出现在命令的正常输出中。如果您想确定,请为每个命令打开单个exec
频道,而不是使用shell频道。我认为shell通道主要用于人类用户的交互式使用。
答案 2 :(得分:8)
我挣扎了半天让JSCH无需使用System.in作为输入流就无济于事。我尝试了Ganymed http://www.ganymed.ethz.ch/ssh2/并在5分钟内完成了它。 所有示例似乎都针对应用程序的一种用法,并且没有示例显示我需要的内容。 Ganymed的例子Basic.java Baaaboof拥有我需要的一切。
答案 3 :(得分:8)
用法:
f = open("test.txt", "r")
x = int(f.readlines()[0]) #assume thet text contains one line with number 1
f.close()
f = open("test.txt", "w")
x += 1
f.write(str(x))
实现:
String remoteCommandOutput = exec("ssh://user:pass@host/work/dir/path", "ls -t | head -n1");
String remoteShellOutput = shell("ssh://user:pass@host/work/dir/path", "ls");
shell("ssh://user:pass@host/work/dir/path", "ls", System.out);
shell("ssh://user:pass@host", System.in, System.out);
sftp("file:/C:/home/file.txt", "ssh://user:pass@host/home");
sftp("ssh://user:pass@host/home/file.txt", "file:/C:/home");
答案 4 :(得分:6)
使用java中的ssh不应该像jsch那样难。 sshj可能会更好。
答案 5 :(得分:2)
编写gritty终端以使用Jsch,但具有更好的处理和vt102仿真。你可以看看那里的代码。我们使用它并且它工作正常。
答案 6 :(得分:2)
我从2000年左右开始使用JSCH,但仍然觉得它很好用。我同意它没有足够的文档记录,但提供的示例似乎足以理解在几分钟内需要,并且用户友好的Swing,虽然这是非常原始的方法,允许快速测试示例以确保它实际工作。并不总是每个好的项目需要的文档数量是编写的代码量的三倍,即使存在这样的代码,这并不总是有助于更快地编写概念的工作原型。
答案 7 :(得分:2)
请注意,当响应有一些延迟时,Charity Leschinski的回答可能会有一些问题。例如:
lparstat 1 5 返回一条响应行并正常工作,
lparstat 5 1 应该返回5行,但只返回第一行
我把命令输出放在另一个内...我确定有更好的方法,我必须这样做才能快速修复
while (commandOutput.available() > 0) {
while (readByte != 0xffffffff) {
outputBuffer.append((char) readByte);
readByte = commandOutput.read();
}
try {Thread.sleep(1000);} catch (Exception ee) {}
}
答案 8 :(得分:1)
Mykhaylo Adamovych提供的示例非常详尽,并展示了 JSch 的大多数主要功能。我将此代码(当然带有署名)打包到一个名为Remote Session的开源库中。我添加了JavaDoc和自定义例外,还提供了一种用于指定自定义会话参数( RemoteConfig )的工具。
Mykhaylo的代码未演示的一个功能是如何为远程系统交互提供“身份”。如果您要执行需要超级用户访问权限的命令(即- sudo ),则这一点至关重要。 远程会话在其 SessionHolder.newSession()实现中添加了此功能:
RemoteConfig remoteConfig = RemoteConfig.getConfig();
Path keyPath = remoteConfig.getKeyPath();
if (keyPath == null) {
throw new RemoteCredentialsUnspecifiedException();
}
String keyPass = remoteConfig.getString(RemoteSettings.SSH_KEY_PASS.key());
if (keyPass != null) {
Path pubPath = keyPath.resolveSibling(keyPath.getFileName() + ".pub");
jsch.addIdentity(keyPath.toString(), pubPath.toString(), keyPass.getBytes());
} else {
jsch.addIdentity(keyPath.toString());
}
请注意,如果远程系统URL包含凭据,则会绕过此行为。
远程会话演示的另一个功能是如何提供已知主机文件:
if ( ! remoteConfig.getBoolean(RemoteSettings.IGNORE_KNOWN_HOSTS.key())) {
Path knownHosts = keyPath.resolveSibling("known_hosts");
if (knownHosts.toFile().exists()) {
jsch.setKnownHosts(knownHosts.toString());
}
}
远程会话还添加了一个 ChannelStream 类,该类封装了与此会话连接的频道的输入/输出操作。这样就可以累积远程会话的输出,直到收到指定的提示为止:
private boolean appendAndCheckFor(String prompt, StringBuilder input, Logger logger) throws InterruptedException, IOException {
String recv = readChannel(false);
if ( ! ((recv == null) || recv.isEmpty())) {
input.append(recv);
if (logger != null) {
logger.debug(recv);
}
if (input.toString().contains(prompt)) {
return false;
}
}
return !channel.isClosed();
}
没什么复杂的,但这可以大大简化交互式远程操作的实现。