使用JSch通过SSH运行命令

时间:2010-03-09 00:40:25

标签: java ssh jsch

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

9 个答案:

答案 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();
}

没什么复杂的,但这可以大大简化交互式远程操作的实现。