使用JSch通过SSH运行命令

使用JSch通过SSH运行命令

问题描述:

我试图通过SSH与JSch运行命令,但JSch几乎没有任何文档,我发现的例子很糟糕。例如,this one不显示处理输出流的代码。并且,this one使用丑陋的黑客知道何时停止从输出流中读取数据。使用JSch通过SSH运行命令

以下用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); 
    } 
+0

'InputStream commandOutput'似乎没有明确关闭。它会造成任何泄漏? – stanleyxu2005 2014-03-19 06:38:18

+0

@ stanleyxu2005 http://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#close%28%29 InputStream的close方法什么也不做。 – 2014-04-22 01:30:58

+2

我希望我可以给你更多的答案 – solti 2014-04-28 17:53:08

gritty终端的编写使用Jsch,但更好的处理和vt102仿真。你可以看看那里的代码。我们使用它,它工作得很好。

使用来自java的ssh应该不会像jsch那样难。你可能会更好用sshj

+0

谢谢,我会尝试了这一点,当我有机会 – jshen 2010-03-13 05:45:08

这是一个无耻的插件,但我现在只是writing一些广泛的Javadoc for JSch

另外,现在在JSch Wiki中有一个Manual(主要由我编写)。


关于原始问题,没有真正的处理流的例子。 一如既往地完成流的读/写。

但是,根本不可能有一个确定的方法来通过读取shell的输出(这与SSH协议无关)知道shell中的一条命令何时完成。

如果shell是交互式的,即它有一个终端连接,它通常会打印一个提示符,您可以尝试识别。但至少理论上这个提示字符串也可能发生在命令的正常输出中。如果您想确定,请为每个命令打开单个exec通道,而不是使用shell通道。我认为,shell频道主要用于人类用户的交互式使用。

我挣扎了半天才得到JSCH的工作,而没有使用System.in作为输入流无济于事。我尝试了Ganymed http://www.ganymed.ethz.ch/ssh2/,并在5分钟内完成。所有的例子似乎都针对应用程序的一种用法,没有任何例子显示我需要什么。 Ganymed的例子Basic.java Baaaboof拥有我需要的一切。

我从2000年开始就使用JSCH,但仍然觉得它是一个很好的库。我同意它没有被很好地记录下来,但提供的示例看起来足够好,足以在几分钟内理解这一点,而且用户友好的Swing虽然是非常原始的方法,但可以快速测试示例以确保其实际工作。并不总是这样,每个优秀的项目都需要比写入代码量多三倍的文档,即使存在这样的代码,这并不总是有助于更快地编写概念的工作原型。

+0

如果你正在寻找JSCH..this的文档可以帮助你:http://epaul.github.io/jsch-documentation/javadoc/com/jcraft/jsch/Session.html – 2016-03-23 04:45:04

用法:

String remoteCommandOutput = exec("ssh://user:[email protected]/work/dir/path", "ls -t | head -n1"); 
String remoteShellOutput = shell("ssh://user:[email protected]/work/dir/path", "ls"); 
shell("ssh://user:[email protected]/work/dir/path", "ls", System.out); 
shell("ssh://user:[email protected]", System.in, System.out); 
sftp("file:/C:/home/file.txt", "ssh://user:[email protected]/home"); 
sftp("ssh://user:[email protected]/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:[email protected]/home"); 
    * sftp("ssh://user:[email protected]/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:[email protected]", 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:[email protected]/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:[email protected]/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:[email protected]/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(":[^:]*[email protected]", "@"); 
     } 

     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; 
     } 
    } 
} 

注意,慈善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) {} 
     }