web-dev-qa-db-ja.com

JSchを使用してSSH経由でコマンドを実行する

JSchを使用してSSH経由でコマンドを実行しようとしていますが、JSchにはほとんどドキュメントがなく、見つけた例はひどいものです。たとえば、 this one は、出力ストリームを処理するためのコードを表示しません。そして、 this one は、いハックを使用して、出力ストリームからの読み取りをいつ停止するかを判断します。

60
jshen

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

これは恥知らずのプラグですが、私はちょうど今です writing いくつかの広範な JSchのJavadoc

また、JSch Wikiには Manual があります(主に私が作成)。


元の質問については、実際にストリームを処理する例はありません。ストリームの読み取り/書き込みはいつものように行われます。

ただし、シェルの出力を読み取っただけで、シェル内の1つのコマンドがいつ終了したかを知る確実な方法はありません(これはSSHプロトコルとは無関係です)。

シェルが対話型である場合、つまり、端末に接続されている場合、通常はプロンプトを出力します。しかし、少なくとも理論的には、このプロンプト文字列は、コマンドからの通常の出力でも発生する可能性があります。確認したい場合は、シェルチャネルを使用する代わりに、コマンドごとに個別のexecチャネルを開きます。 Shellチャネルは、主に人間のユーザーによるインタラクティブな使用に使用されると思います。

30
Paŭlo Ebermann

使用法:

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

System.inを入力ストリームとして使用せずにJSCHを機能させるために半日苦労しました。 Ganymedを試してみました http://www.ganymed.ethz.ch/ssh2/ で、5分で実行しました。すべての例は、アプリの1つの使用法を対象としているようで、どの例も必要なものを示していません。 GanymedのサンプルBasic.Java Baaaboofには必要なものがすべて揃っています。

8
Jay Shepherd

Javaからjshがするほど難しくないはずです。 sshj を使用する方が良いかもしれません。

6
shikhar

2000年頃からJSCHを使用していますが、それでも使用に適したライブラリです。私はそれが十分に文書化されていないことに同意しますが、提供された例は数分で必要なことを理解するのに十分に思えます、そしてこれは非常に独創的なアプローチですが、実際に動作することを確認するために例をテストすることができますすべての良いプロジェクトが、記述されたコードの量の3倍以上のドキュメントを必要とすることは必ずしも真実ではありません。また、それが存在する場合でも、コンセプトの実用プロトタイプをより速く書くのに必ずしも役立つとは限りません。

2
audriusa

gritty ターミナルはJschを使用するように書かれていますが、処理とvt102エミュレーションが改善されています。そこのコードを見ることができます。私たちはそれを使用し、それはうまく動作します。

2
omerkudat

応答に多少の遅延がある場合、チャリティレシンスキーの答えには少し問題があることに注意してください。例えば:
lparstat 1 5は1行の応答行を返し、動作します。
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) {}
        }
2
MetalRules