Exécuter une commande sur SSH avec JSch

J’essaie d’exécuter une commande sur SSH avec JSch, mais JSch n’a pratiquement aucune documentation et les exemples que j’ai trouvés sont terribles. Par exemple, celui-ci n’affiche pas de code pour gérer le stream de sortie. Et, celui-ci utilise un hack laid pour savoir quand arrêter de lire le stream de sortie.

L’exemple de code suivant écrit en Java vous permettra d’exécuter n’importe quelle commande sur un ordinateur étranger via SSH depuis un programme Java. Vous devrez inclure le fichier 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 Ssortingng strUserName; private Ssortingng strConnectionIP; private int intConnectionPort; private Ssortingng strPassword; private Session sesConnection; private int intTimeOut; private void doCommonConstructorActions(Ssortingng userName, Ssortingng password, Ssortingng connectionIP, Ssortingng knownHostsFileName) { jschSSHChannel = new JSch(); try { jschSSHChannel.setKnownHosts(knownHostsFileName); } catch(JSchException jschX) { logError(jschX.getMessage()); } strUserName = userName; strPassword = password; strConnectionIP = connectionIP; } public SSHManager(Ssortingng userName, Ssortingng password, Ssortingng connectionIP, Ssortingng knownHostsFileName) { doCommonConstructorActions(userName, password, connectionIP, knownHostsFileName); intConnectionPort = 22; intTimeOut = 60000; } public SSHManager(Ssortingng userName, Ssortingng password, Ssortingng connectionIP, Ssortingng knownHostsFileName, int connectionPort) { doCommonConstructorActions(userName, password, connectionIP, knownHostsFileName); intConnectionPort = connectionPort; intTimeOut = 60000; } public SSHManager(Ssortingng userName, Ssortingng password, Ssortingng connectionIP, Ssortingng knownHostsFileName, int connectionPort, int timeOutMilliseconds) { doCommonConstructorActions(userName, password, connectionIP, knownHostsFileName); intConnectionPort = connectionPort; intTimeOut = timeOutMilliseconds; } public Ssortingng connect() { Ssortingng 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("SsortingctHostKeyChecking", "no"); sesConnection.connect(intTimeOut); } catch(JSchException jschX) { errorMessage = jschX.getMessage(); } return errorMessage; } private Ssortingng logError(Ssortingng errorMessage) { if(errorMessage != null) { LOGGER.log(Level.SEVERE, "{0}:{1} - {2}", new Object[]{strConnectionIP, intConnectionPort, errorMessage}); } return errorMessage; } private Ssortingng logWarning(Ssortingng warnMessage) { if(warnMessage != null) { LOGGER.log(Level.WARNING, "{0}:{1} - {2}", new Object[]{strConnectionIP, intConnectionPort, warnMessage}); } return warnMessage; } public Ssortingng sendCommand(Ssortingng command) { SsortingngBuilder outputBuffer = new SsortingngBuilder(); 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.toSsortingng(); } public void close() { sesConnection.disconnect(); } } 

Pour tester.

  /** * 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 **/ Ssortingng command = "ls FILE_NAME"; Ssortingng userName = "USER"; Ssortingng password = "PASSWORD"; Ssortingng connectionIP = "HOST"; SSHManager instance = new SSHManager(userName, password, connectionIP, ""); Ssortingng errorMessage = instance.connect(); if(errorMessage != null) { System.out.println(errorMessage); fail(); } Ssortingng expResult = "FILE_NAME\n"; // call sendCommand for each command and the output //(without prompts) is returned Ssortingng result = instance.sendCommand(command); // close only after all commands are sent instance.close(); assertEquals(expResult, result); } 

Ceci est un plug sans vergogne, mais je viens d’ écrire quelques javadoc pour JSch .

En outre, il existe maintenant un manuel dans le wiki JSch (écrit principalement par moi).


A propos de la question initiale, il n’y a pas vraiment d’exemple pour gérer les stream. Lire / écrire un stream se fait comme toujours.

Mais il n’y a tout simplement pas de moyen sûr de savoir quand une commande dans un shell a fini de lire la sortie du shell (ceci est indépendant du protocole SSH).

Si le shell est interactif, c’est-à-dire qu’un terminal est connecté, il affiche généralement une invite que vous pouvez essayer de reconnaître. Mais au moins théoriquement, cette chaîne d’invite pourrait également apparaître en sortie normale d’une commande. Si vous voulez en être sûr, ouvrez des canaux exec individuels pour chaque commande au lieu d’utiliser un canal shell. Le canal shell est principalement utilisé pour une utilisation interactive par un utilisateur humain, je pense.

Pendant une demi-journée, j’ai eu du mal à faire fonctionner JSCH sans utiliser System.in comme stream d’entrée en vain. J’ai essayé Ganymed http://www.ganymed.ethz.ch/ssh2/ et je l’ai fait en 5 minutes. Tous les exemples semblent viser une utilisation de l’application et aucun des exemples n’a montré ce dont j’avais besoin. L’exemple de Ganymed Basic.java Baaaboof A tout ce dont j’ai besoin.

Usage:

 Ssortingng remoteCommandOutput = exec("ssh://user:pass@host/work/dir/path", "ls -t | head -n1"); Ssortingng 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"); 

La mise en oeuvre:

 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.SsortingngUtils.sortingm; 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 Ssortingng SSH = "ssh"; private static final Ssortingng FILE = "file"; private SshUtils() { } /** * 
 *  * sftp("file:/C:/home/file.txt", "ssh://user:pass@host/home"); * sftp("ssh://user:pass@host/home/file.txt", "file:/C:/home"); *  * * 
 * * @param fromUri * file * @param toUri * directory */ public static void sftp(Ssortingng fromUri, Ssortingng 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 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 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); } } /** * 
 *  * shell("ssh://user:pass@host", System.in, System.out); *  * 

*/ public static void shell(Ssortingng connectUri, InputStream is, OutputStream os) { try (SessionHolder session = new SessionHolder<>("shell", URI.create(connectUri))) { shell(session, is, os); } } /** *

 *  * Ssortingng remoteOutput = shell("ssh://user:pass@host/work/dir/path", "ls") *  * 

*/ public static Ssortingng shell(Ssortingng connectUri, Ssortingng command) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { shell(connectUri, command, baos); return baos.toSsortingng(); } catch (RuntimeException e) { LOG.warn(baos.toSsortingng()); throw e; } } /** *

 *  * shell("ssh://user:pass@host/work/dir/path", "ls", System.out) *  * 

*/ public static void shell(Ssortingng connectUri, Ssortingng 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(script); pw.println("exit"); pw.flush(); shell(session, in, out); } catch (IOException e) { throw new RuntimeException(e); } } private static void shell(SessionHolder 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); } } /** *

 *  * System.out.println(exec("ssh://user:pass@host/work/dir/path", "ls -t | head -n1")); *  * * 
 * * @param connectUri * @param command * @return */ public static Ssortingng exec(Ssortingng connectUri, Ssortingng command) { try (SessionHolder 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 session, Ssortingng 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(); Ssortingng output = IOUtils.toSsortingng(is); session.assertExitStatus(IOUtils.toSsortingng(errIs)); return sortingm(output); } catch (InterruptedException | JSchException | IOException e) { throw new RuntimeException("Cannot execute command", e); } } public static class SessionHolder 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 Ssortingng channelType; private URI uri; private Session session; private C channel; public SessionHolder(Ssortingng channelType, URI uri) { this(channelType, uri, ImmutableMap.of("SsortingctHostKeyChecking", "no")); } public SessionHolder(Ssortingng channelType, URI uri, Map props) { this.channelType = channelType; this.uri = uri; this.session = newSession(props); this.channel = newChannel(session); } private Session newSession(Map 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(Ssortingng 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 Ssortingng getMaskedUri() { return uri.toSsortingng().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; } } }

utiliser ssh de java ne devrait pas être aussi difficile que jsch le fait. vous pourriez être mieux avec sshj .

Le terminal gritty a été écrit pour utiliser Jsch, mais avec une meilleure gestion et une meilleure émulation vt102. Vous pouvez consulter le code ici. Nous l’utilisons et cela fonctionne très bien.

J’utilise JSCH depuis environ 2000 et je le trouve toujours une bonne bibliothèque à utiliser. Je conviens que ce n’est pas assez documenté, mais les exemples fournis semblent assez bons pour être compris en quelques minutes, et Swing convivial, bien que ce soit une approche assez originale, permet de tester l’exemple rapidement pour s’assurer qu’il fonctionne réellement. Il n’est pas toujours vrai que chaque bon projet a besoin de trois fois plus de documentation que la quantité de code écrite, et même si tel est le cas, cela ne permet pas toujours d’écrire plus rapidement un prototype fonctionnel de votre concept.

Notez que la réponse de Charity Leschinski peut poser un problème quand il y a un certain retard dans la réponse. par exemple:
lparstat 1 5 renvoie une ligne de réponse et fonctionne,
lparstat 5 1 devrait renvoyer 5 lignes, mais ne renvoie que la première

J’ai mis la sortie de commande à l’intérieur d’un autre … Je suis sûr qu’il y a une meilleure solution, je devais le faire comme solution rapide

  while (commandOutput.available() > 0) { while (readByte != 0xffffffff) { outputBuffer.append((char) readByte); readByte = commandOutput.read(); } try {Thread.sleep(1000);} catch (Exception ee) {} }