2010-03-09 196 views
48

我試圖通過SSH與JSch運行命令,但JSch幾乎沒有任何文檔,我發現的例子很糟糕。例如,this one不顯示處理輸出流的代碼。並且,this one使用醜陋的黑客知道何時停止從輸出流中讀取數據。使用JSch通過SSH運行命令

回答

74

以下用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

2

gritty終端的編寫使用Jsch,但更好的處理和vt102仿真。你可以看看那裏的代碼。我們使用它,它工作得很好。

5

使用來自java的ssh應該不會像jsch那樣難。你可能會更好用sshj

+0

謝謝,我會嘗試了這一點,當我有機會 – jshen 2010-03-13 05:45:08

27

這是一個無恥的插件,但我現在只是writing一些廣泛的Javadoc for JSch

另外,現在在JSch Wiki中有一個Manual(主要由我編寫)。


關於原始問題,沒有真正的處理流的例子。 一如既往地完成流的讀/寫。

但是,根本不可能有一個確定的方法來通過讀取shell的輸出(這與SSH協議無關)知道shell中的一條命令何時完成。

如果shell是交互式的,即它有一個終端連接,它通常會打印一個提示符,您可以嘗試識別。但至少理論上這個提示字符串也可能發生在命令的正常輸出中。如果您想確定,請爲每個命令打開單個exec通道,而不是使用shell通道。我認爲,shell頻道主要用於人類用戶的交互式使用。

8

我掙扎了半天才得到JSCH的工作,而沒有使用System.in作爲輸入流無濟於事。我嘗試了Ganymed http://www.ganymed.ethz.ch/ssh2/,並在5分鐘內完成。所有的例子似乎都針對應用程序的一種用法,沒有任何例子顯示我需要什麼。 Ganymed的例子Basic.java Baaaboof擁有我需要的一切。

2

我從2000年開始就使用JSCH,但仍然覺得它是一個很好的庫。我同意它沒有被很好地記錄下來,但提供的示例看起來足夠好,足以在幾分鐘內理解這一點,而且用戶友好的Swing雖然是非常原始的方法,但可以快速測試示例以確保其實際工作。並不總是這樣,每個優秀的項目都需要比寫入代碼量多三倍的文檔,即使存在這樣的代碼,這並不總是有助於更快地編寫概念的工作原型。

+0

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

7

用法:

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

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