Frage

Ich versuche, einen Befehl über SSH mit JSch zu laufen, aber JSch hat praktisch keine Dokumentation und Beispiele, die ich gefunden habe, sind schrecklich. Zum Beispiel dieses keinen Programmcode zeigt den Ausgangsstrom für die Handhabung. Und dieses eine hässliche Hack verwendet wissen wann man aufhören muss aus dem Ausgangsstrom zu lesen.

War es hilfreich?

Lösung

Das folgende Codebeispiel in Java geschrieben ermöglicht es Ihnen, innerhalb eines Java-Programms einen Befehl auf einem fremden Computer über SSH auszuführen. Sie werden die com.jcraft.jsch JAR-Datei enthalten müssen.

  /* 
  * 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();
  }

  }

Für den Test.

  /**
     * 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);
  }

Andere Tipps

Dies ist eine schamlose Werbung, aber ich bin jetzt gerade schreiben einige umfangreiche Javadoc für JSch .

Außerdem gibt es jetzt ein Handbuch im JSch Wiki (geschrieben vor allem von mir).


über die ursprüngliche Frage, ist es nicht wirklich ein Beispiel für die Ströme der Handhabung. Lesen / Schreiben eines Stroms erfolgt wie immer.

Aber es kann nicht einfach ein sicherer Weg, zu wissen, wenn ein Befehl in einer Shell nur aus der Lektüre des Shell-Ausgabe beendet hat (dies ist unabhängig von dem SSH-Protokoll).

Wenn die Shell interaktiv ist, das heißt, es einen Terminal hat angebracht ist, wird es gedruckt in der Regel eine Aufforderung, die Sie könnten versuchen, zu erkennen. Aber zumindest theoretisch das Prompt-String auch in normalen Ausgabe eines Befehls auftreten können. Wenn Sie möchten, für jeden Befehl sicher, offene Einzel exec Kanäle sein, anstatt einen Shell-Kanal zu verwenden. Der Shell-Kanal ist vor allem für die interaktive Nutzung durch einen menschlichen Benutzer verwendet, denke ich.

Ich kämpfte für einen halben Tag zu JSch ohne Erfolg ohne Verwendung des System.in als Eingangsstrom an der Arbeit. Ich habe versucht, Ganymed http://www.ganymed.ethz.ch/ssh2/ und es hatte gehen in 5 Minuten. Alle scheinen die Beispiele bei einer Verwendung der App und keines der Beispiele anzustreben zeigte, was ich brauchte. Ganymed dem Beispiel Basic.java Baaaboof Hat alles, was ich brauche.

Verbrauch:

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

Umsetzung:

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

mit ssh von Java sollte nicht so schwer sein, wie jsch es macht. Sie könnte besser sein mit sshj .

Die gritty Terminal geschrieben wurde JSch zu verwenden, aber mit einer besseren Handhabung und VT102-Emulation . Sie können es auf den Code werfen Sie einen Blick. Wir verwenden es, und es funktioniert gut.

Ich bin mit JSch seit etwa 2000 und es immer noch eine gute Bibliothek finden zu verwenden. Ich bin damit einverstanden ist es nicht gut genug dokumentiert, aber die zur Verfügung gestellten Beispiele scheinen gut genug, um zu verstehen, dass in einigen Minuten erforderlich ist, und benutzerfreundlicher Swing, während der ganz ursprünglicher Ansatz ist, ermöglicht es schnell um das Beispiel zu testen, um sicherzustellen, dass es tatsächlich funktioniert. Es ist nicht immer wahr, dass jedes gutes Projekt dreimal mehr Dokumentation als die Menge an Code muss geschrieben werden, und selbst wenn eine solche vorhanden ist, ist dies nicht immer hilft, schneller einen funktionierenden Prototyp Ihres Konzeptes zu schreiben.

Beachten Sie, dass Charity Leschinski Antwort ein bisschen ein Problem haben, wenn es eine gewisse Verzögerung in der Antwort ist. zB:
lparstat 1 5 gibt eine Antwortzeile und Werke,
lparstat 5 1 sollte 5 Zeilen zurück, sondern liefert nur die erste Seite

Ich habe den Ausgabebefehl setzen, während in einer anderen ... Ich bin sicher, dass es ein besserer Weg, ich hatte dies als eine schnelle Lösung zu tun

        while (commandOutput.available() > 0) {
            while (readByte != 0xffffffff) {
                outputBuffer.append((char) readByte);
                readByte = commandOutput.read();
            }
            try {Thread.sleep(1000);} catch (Exception ee) {}
        }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top