Запустите команду через SSH с JSch - PullRequest
59 голосов
/ 09 марта 2010

Я пытаюсь запустить команду через SSH с JSch, но у JSch практически нет документации, и примеры, которые я нашел, ужасны. Например, этот не показывает код для обработки выходного потока. И этот использует некрасивый хак, чтобы узнать, когда прекратить чтение из выходного потока.

Ответы [ 8 ]

90 голосов
/ 10 августа 2012

Следующий пример кода, написанный на Java, позволит вам выполнить любую команду на чужом компьютере через SSH из Java-программы. Вам нужно будет включить файл jar com.jcraft.jsch.

  /* 
  * 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);
  }
29 голосов
/ 12 апреля 2011

Это бесстыдный плагин, но я только сейчас пишу какой-то обширный Javadoc для JSch .

Кроме того, теперь в JSch Wiki есть Руководство (написанное в основном мной).


Что касается исходного вопроса, на самом деле нет примера для обработки потоков. Чтение / запись потока выполняется как всегда.

Но просто не может быть точного способа узнать, когда одна команда в оболочке закончила только чтение результата оболочки (это не зависит от протокола SSH).

Если оболочка является интерактивной, то есть к ней подключен терминал, она обычно выводит подсказку, которую вы можете попытаться распознать. Но, по крайней мере, теоретически эта строка приглашения может также появляться в обычном выводе команды. Если вы хотите быть уверенным, открывайте отдельные каналы exec для каждой команды вместо использования канала оболочки. Я думаю, что канал оболочки в основном используется для интерактивного использования человеком.

8 голосов
/ 06 мая 2016

Использование:

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");

Реализация:

<code>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>
     * 
* / общедоступная статическая пустая оболочка (String connectUri, InputStream is, OutputStream os) { try (SessionHolder session = new SessionHolder <> ("shell", URI.create (connectUri))) { shell (сессия, is, os); } } / ** *
     * <code>
     * String remoteOutput = shell("ssh://user:pass@host/work/dir/path", "ls")
     * </code>
     * 
* / public static String shell (String connectUri, String команда) { ByteArrayOutputStream baos = new ByteArrayOutputStream (); пытаться { оболочка (connectUri, команда, baos); return baos.toString (); } catch (RuntimeException e) { LOG.warn (baos.toString ()); бросить е; } } / ** *
     * <code>
     * shell("ssh://user:pass@host/work/dir/path", "ls", System.out)
     * </code>
     * 
* / public static void shell (String connectUri, String script, OutputStream out) { try (SessionHolder 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 (сценарий); pw.println ( "выход"); pw.flush (); оболочка (сессия, вход, выход); } catch (IOException e) { бросить новое RuntimeException (e); } } частная статическая пустая оболочка (SessionHolder сеанс, InputStream is, OutputStream os) { пытаться { ChannelShell channel = session.getChannel (); channel.setInputStream (есть, правда); channel.setOutputStream (os, true); LOG.info («Стартовая оболочка для» + session.getMaskedUri ()); session.execute (); session.assertExitStatus («Проверьте выходные данные оболочки на наличие ошибок.»); } catch (InterruptedException | JSchException e) { бросить новое RuntimeException («Невозможно выполнить скрипт», e); } } / ** *
     * <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;
        }
    }
}
</code>
8 голосов
/ 17 мая 2011

Я боролся полдня, чтобы заставить JSCH работать без использования System.in в качестве входного потока, но безрезультатно. Я попробовал Ganymed http://www.ganymed.ethz.ch/ssh2/ и у меня получилось 5 минут. Все примеры, похоже, направлены на одно использование приложения, и ни один из примеров не показал, что мне нужно. Пример Ганимеда Basic.java Baaaboof Есть все, что мне нужно.

6 голосов
/ 13 марта 2010

использование ssh из java не должно быть таким сложным, как это делает jsch. вам может быть лучше с sshj .

2 голосов
/ 31 октября 2017

Обратите внимание, что ответ 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) {}
        }
2 голосов
/ 23 мая 2012

Я пользуюсь JSCH с 2000 года и до сих пор считаю, что это хорошая библиотека для использования. Я согласен, что он недостаточно хорошо документирован, но представленные примеры кажутся достаточно хорошими, чтобы понять, что требуется через несколько минут, и удобный для пользователя Swing, хотя это довольно оригинальный подход, позволяет быстро протестировать пример, чтобы убедиться, что он действительно работает. Не всегда верно, что каждый хороший проект требует в три раза больше документации, чем объем написанного кода, и даже если таковой имеется, это не всегда помогает быстрее написать работающий прототип вашей концепции.

2 голосов
/ 09 марта 2010

Терминал gritty был написан для использования Jsch, но с лучшей обработкой и эмуляцией vt102. Вы можете взглянуть на код там. Мы используем его, и он прекрасно работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...