使用 JSch 通过 SSH 运行命令
Posted
技术标签:
【中文标题】使用 JSch 通过 SSH 运行命令【英文标题】:Run a command over SSH with JSch 【发布时间】:2011-01-25 05:47:01 【问题描述】:我正在尝试,但 JSch 几乎没有文档,而且我发现的示例很糟糕。例如,this one 不显示用于处理输出流的代码。而且,this one 使用了一个丑陋的 hack 来知道何时停止从输出流中读取。
【问题讨论】:
另见How to read JSch command output? - 它展示了如何正确同时读取标准和错误输出,以允许命令完成并收集包括错误在内的所有输出。跨度> 【参考方案1】:gritty 终端是为使用 Jsch 而编写的,但具有更好的处理和 vt102 仿真。你可以看看那里的代码。我们使用它,它工作得很好。
【讨论】:
但是,从 GitHub 访问它更容易(例如 - github.com/xliangwu/gritty)。【参考方案2】:从 java 中使用 ssh 不应该像 jsch 那样难。 sshj 可能会更好。
【讨论】:
【参考方案3】:这是一个无耻的插件,但我现在只是writing一些广泛的Javadoc for JSch。
另外,JSch Wiki 中现在有一个Manual(主要由我编写)。
关于原始问题,实际上并没有处理流的示例。 像往常一样读取/写入流。
但是,仅仅通过读取 shell 的输出(这与 SSH 协议无关),就无法确定 shell 中的一个命令何时完成。
如果 shell 是交互式的,即它连接了一个终端,它通常会打印一个提示,您可以尝试识别它。但至少从理论上讲,这个提示字符串也可能出现在命令的正常输出中。如果您想确定,请为每个命令打开单独的 exec
通道,而不是使用 shell 通道。我认为 shell 通道主要用于人类用户的交互使用。
【讨论】:
这非常需要,谢谢。 JSCH 是一个好主意,但似乎是一个零文档的废弃库,有时让我感到莫名其妙的是人们实际上在关键项目中使用它。【参考方案4】:在不使用 System.in 作为输入流的情况下,我挣扎了半天让 JSCH 工作,但无济于事。我尝试了 Ganymed http://www.ganymed.ethz.ch/ssh2/ 并在 5 分钟内完成。 所有示例似乎都针对该应用程序的一种用法,并且没有一个示例显示我需要什么。 Ganymed 的示例 Basic.java Baaaboof 有我需要的一切。
【讨论】:
【参考方案5】:我从大约 2000 年开始使用 JSCH,但仍然觉得它是一个很好的库。我同意它的文档记录不够好,但所提供的示例似乎足以在几分钟内理解这是必需的,并且用户友好的 Swing 虽然这是一种非常原始的方法,但允许快速测试示例以确保它确实有效。并非每个好的项目都需要比编写的代码量多三倍的文档,即使存在这种情况,这也并不总是有助于更快地编写概念的工作原型。
【讨论】:
如果您正在寻找 JSCH 的文档。这可能会对您有所帮助:epaul.github.io/jsch-documentation/javadoc/com/jcraft/jsch/…【参考方案6】:以下用 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);
【讨论】:
InputStream commandOutput
似乎没有明确关闭。它会造成任何泄漏吗?
@stanleyxu2005 docs.oracle.com/javase/7/docs/api/java/io/… InputStream 的 close 方法什么都不做。
根据 Java 文档,channel.getInputStream() 必须在 channel.connect() 之前:epaul.github.io/jsch-documentation/javadoc/com/jcraft/jsch/… 我不知道为什么...
我发现你的回答很有用,但是这会返回给我readByte=-1
,我完全不明白为什么。
@richmondwang:如果readByte=-1
那么它就是流的结尾。如果您遇到问题,请提出新问题。【参考方案7】:
用法:
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");
实施:
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Thread.sleep;
import static org.apache.commons.io.FilenameUtils.getFullPath;
import static org.apache.commons.io.FilenameUtils.getName;
import static org.apache.commons.lang3.StringUtils.trim;
import com.google.common.collect.ImmutableMap;
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.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.util.Map;
import java.util.Properties;
public final class SshUtils
private static final Logger LOG = LoggerFactory.getLogger(SshUtils.class);
private static final String SSH = "ssh";
private static final String FILE = "file";
private SshUtils()
/**
* <pre>
* <code>
* sftp("file:/C:/home/file.txt", "ssh://user:pass@host/home");
* sftp("ssh://user:pass@host/home/file.txt", "file:/C:/home");
* </code>
*
* <pre>
*
* @param fromUri
* file
* @param toUri
* directory
*/
public static void sftp(String fromUri, String toUri)
URI from = URI.create(fromUri);
URI to = URI.create(toUri);
if (SSH.equals(to.getScheme()) && FILE.equals(from.getScheme()))
upload(from, to);
else if (SSH.equals(from.getScheme()) && FILE.equals(to.getScheme()))
download(from, to);
else
throw new IllegalArgumentException();
private static void upload(URI from, URI to)
try (SessionHolder<ChannelSftp> session = new SessionHolder<>("sftp", to);
FileInputStream fis = new FileInputStream(new File(from)))
LOG.info("Uploading --> ", from, session.getMaskedUri());
ChannelSftp channel = session.getChannel();
channel.connect();
channel.cd(to.getPath());
channel.put(fis, getName(from.getPath()));
catch (Exception e)
throw new RuntimeException("Cannot upload file", e);
private static void download(URI from, URI to)
File out = new File(new File(to), getName(from.getPath()));
try (SessionHolder<ChannelSftp> session = new SessionHolder<>("sftp", from);
OutputStream os = new FileOutputStream(out);
BufferedOutputStream bos = new BufferedOutputStream(os))
LOG.info("Downloading --> ", session.getMaskedUri(), to);
ChannelSftp channel = session.getChannel();
channel.connect();
channel.cd(getFullPath(from.getPath()));
channel.get(getName(from.getPath()), bos);
catch (Exception e)
throw new RuntimeException("Cannot download file", e);
/**
* <pre>
* <code>
* shell("ssh://user:pass@host", System.in, System.out);
* </code>
* </pre>
*/
public static void shell(String connectUri, InputStream is, OutputStream os)
try (SessionHolder<ChannelShell> session = new SessionHolder<>("shell", URI.create(connectUri)))
shell(session, is, os);
/**
* <pre>
* <code>
* String remoteOutput = shell("ssh://user:pass@host/work/dir/path", "ls")
* </code>
* </pre>
*/
public static String shell(String connectUri, String command)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try
shell(connectUri, command, baos);
return baos.toString();
catch (RuntimeException e)
LOG.warn(baos.toString());
throw e;
/**
* <pre>
* <code>
* shell("ssh://user:pass@host/work/dir/path", "ls", System.out)
* </code>
* </pre>
*/
public static void shell(String connectUri, String script, OutputStream out)
try (SessionHolder<ChannelShell> session = new SessionHolder<>("shell", URI.create(connectUri));
PipedOutputStream pipe = new PipedOutputStream();
PipedInputStream in = new PipedInputStream(pipe);
PrintWriter pw = new PrintWriter(pipe))
if (session.getWorkDir() != null)
pw.println("cd " + session.getWorkDir());
pw.println(script);
pw.println("exit");
pw.flush();
shell(session, in, out);
catch (IOException e)
throw new RuntimeException(e);
private static void shell(SessionHolder<ChannelShell> session, InputStream is, OutputStream os)
try
ChannelShell channel = session.getChannel();
channel.setInputStream(is, true);
channel.setOutputStream(os, true);
LOG.info("Starting shell for " + session.getMaskedUri());
session.execute();
session.assertExitStatus("Check shell output for error details.");
catch (InterruptedException | JSchException e)
throw new RuntimeException("Cannot execute script", e);
/**
* <pre>
* <code>
* System.out.println(exec("ssh://user:pass@host/work/dir/path", "ls -t | head -n1"));
* </code>
*
* <pre>
*
* @param connectUri
* @param command
* @return
*/
public static String exec(String connectUri, String command)
try (SessionHolder<ChannelExec> session = new SessionHolder<>("exec", URI.create(connectUri)))
String scriptToExecute = session.getWorkDir() == null
? command
: "cd " + session.getWorkDir() + "\n" + command;
return exec(session, scriptToExecute);
private static String exec(SessionHolder<ChannelExec> session, String command)
try (PipedOutputStream errPipe = new PipedOutputStream();
PipedInputStream errIs = new PipedInputStream(errPipe);
InputStream is = session.getChannel().getInputStream())
ChannelExec channel = session.getChannel();
channel.setInputStream(null);
channel.setErrStream(errPipe);
channel.setCommand(command);
LOG.info("Starting exec for " + session.getMaskedUri());
session.execute();
String output = IOUtils.toString(is);
session.assertExitStatus(IOUtils.toString(errIs));
return trim(output);
catch (InterruptedException | JSchException | IOException e)
throw new RuntimeException("Cannot execute command", e);
public static class SessionHolder<C extends Channel> implements Closeable
private static final int DEFAULT_CONNECT_TIMEOUT = 5000;
private static final int DEFAULT_PORT = 22;
private static final int TERMINAL_HEIGHT = 1000;
private static final int TERMINAL_WIDTH = 1000;
private static final int TERMINAL_WIDTH_IN_PIXELS = 1000;
private static final int TERMINAL_HEIGHT_IN_PIXELS = 1000;
private static final int DEFAULT_WAIT_TIMEOUT = 100;
private String channelType;
private URI uri;
private Session session;
private C channel;
public SessionHolder(String channelType, URI uri)
this(channelType, uri, ImmutableMap.of("StrictHostKeyChecking", "no"));
public SessionHolder(String channelType, URI uri, Map<String, String> props)
this.channelType = channelType;
this.uri = uri;
this.session = newSession(props);
this.channel = newChannel(session);
private Session newSession(Map<String, String> props)
try
Properties config = new Properties();
config.putAll(props);
JSch jsch = new JSch();
Session newSession = jsch.getSession(getUser(), uri.getHost(), getPort());
newSession.setPassword(getPass());
newSession.setUserInfo(new User(getUser(), getPass()));
newSession.setDaemonThread(true);
newSession.setConfig(config);
newSession.connect(DEFAULT_CONNECT_TIMEOUT);
return newSession;
catch (JSchException e)
throw new RuntimeException("Cannot create session for " + getMaskedUri(), e);
@SuppressWarnings("unchecked")
private C newChannel(Session session)
try
Channel newChannel = session.openChannel(channelType);
if (newChannel instanceof ChannelShell)
ChannelShell channelShell = (ChannelShell) newChannel;
channelShell.setPtyType("ANSI", TERMINAL_WIDTH, TERMINAL_HEIGHT, TERMINAL_WIDTH_IN_PIXELS, TERMINAL_HEIGHT_IN_PIXELS);
return (C) newChannel;
catch (JSchException e)
throw new RuntimeException("Cannot create " + channelType + " channel for " + getMaskedUri(), e);
public void assertExitStatus(String failMessage)
checkState(channel.getExitStatus() == 0, "Exit status %s for %s\n%s", channel.getExitStatus(), getMaskedUri(), failMessage);
public void execute() throws JSchException, InterruptedException
channel.connect();
channel.start();
while (!channel.isEOF())
sleep(DEFAULT_WAIT_TIMEOUT);
public Session getSession()
return session;
public C getChannel()
return channel;
@Override
public void close()
if (channel != null)
channel.disconnect();
if (session != null)
session.disconnect();
public String getMaskedUri()
return uri.toString().replaceFirst(":[^:]*?@", "@");
public int getPort()
return uri.getPort() < 0 ? DEFAULT_PORT : uri.getPort();
public String getUser()
return uri.getUserInfo().split(":")[0];
public String getPass()
return uri.getUserInfo().split(":")[1];
public String getWorkDir()
return uri.getPath();
private static class User implements UserInfo, UIKeyboardInteractive
private String user;
private String pass;
public User(String user, String pass)
this.user = user;
this.pass = pass;
@Override
public String getPassword()
return pass;
@Override
public boolean promptYesNo(String str)
return false;
@Override
public String getPassphrase()
return user;
@Override
public boolean promptPassphrase(String message)
return true;
@Override
public boolean promptPassword(String message)
return true;
@Override
public void showMessage(String message)
// do nothing
@Override
public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt, boolean[] echo)
return null;
【讨论】:
我很惊讶在这个答案下没有更多的 cmets。很棒的代码,但我在运行sudo
引导的命令时遇到问题。
@Sedrick - 您可能需要指定一个“身份”,JSch 提供给远程系统以授权您的超级用户访问。我在此代码的扩展(Remote Session 库)中添加了对此功能的支持。
@ScottBabcock,谢谢,我早就想通了。【参考方案8】:
请注意,当响应出现延迟时,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)
【讨论】:
【参考方案9】: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 包含凭据,则会绕过此行为。
远程会话演示的另一个功能是如何提供 known-hosts 文件:
if ( ! remoteConfig.getBoolean(RemoteSettings.IGNORE_KNOWN_HOSTS.key()))
Path knownHosts = keyPath.resolveSibling("known_hosts");
if (knownHosts.toFile().exists())
jsch.setKnownHosts(knownHosts.toString());
Remote Session 还添加了一个 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();
没什么太复杂的,但这可以大大简化交互式远程操作的实现。
【讨论】:
以上是关于使用 JSch 通过 SSH 运行命令的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Java 在远程系统上运行 SSH 命令? [关闭]
某些 Unix 命令在使用 JSch 通过 Java 执行时失败并显示“... not found”