Запустите команду по SSH с помощью JSch
Я пытаюсь запустить команду по SSH с помощью JSch, но JSch практически не имеет документации, и примеры, которые я нашел, ужасны. Например, этот не показывать код для обработки выходного потока. И,этот использует грязный хак, чтобы знать, когда прекратить чтение из выходного потока.
8 ответов:
следующий пример кода, написанный на Java, позволит вам выполнить любую команду на иностранном компьютере через SSH из программы java. Вам нужно будет включить com.jcraft.файл jar 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); }
Это промоушен, но я только сейчас писать огромный Javadoc для JSch.
кроме того, теперь есть руководство В Jsch Wiki (написано в основном мной).
что касается исходного вопроса, то на самом деле нет примера для обработки потоков. Чтение / запись потока выполняется как всегда.
но просто не может быть верного способа узнать, когда одна команда в оболочке закончилась только из чтение вывода оболочки (это не зависит от протокола SSH).
если оболочка интерактивна, т. е. к ней подключен терминал, он обычно печатает приглашение, которое вы можете попытаться распознать. Но, по крайней мере, теоретически эта строка приглашения может также произойти в обычном выводе из команды. Если вы хотите быть уверены, откройте individual
exec
каналы для каждой команды вместо использования канала оболочки. Канал оболочки в основном используется для интерактивного использования пользователем человека, я думать.
Я боролся за полдня, чтобы заставить JSCH работать без использования System.in как входной поток безрезультатно. Я попробовал Ганимед http://www.ganymed.ethz.ch/ssh2/ и это произошло через 5 минут. Все примеры, похоже, направлены на одно использование приложения, и ни один из примеров не показал, что мне нужно. Пример Ганимеда базовый.у java Baaaboof есть все, что мне нужно.
использование:
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; } } }
использование ssh из java не должно быть таким же сложным, как jsch. возможно, вам будет лучше с sshj.
The песчаный терминал был написан для использования Jsch, но с лучшей обработкой и эмуляцией vt102. Вы можете взглянуть на код. Мы используем его и он работает просто отлично.
Я использую JSCH примерно с 2000 года и до сих пор считаю его хорошей библиотекой для использования. Я согласен, что это не документировано достаточно хорошо, но приведенные примеры кажутся достаточно хорошими, чтобы понять, что требуется в течение нескольких минут, и удобный Swing, хотя это довольно оригинальный подход, позволяет быстро проверить пример, чтобы убедиться, что он действительно работает. Не всегда верно, что каждый хороший проект требует в три раза больше документации, чем количество написанного кода, и даже когда такое присутствует, это не всегда помогает быстрее написать рабочий прототип вашей концепции.
обратите внимание, что ответ благотворительности Лещинского может иметь немного проблемы, когда есть некоторая задержка в ответе. например:
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) {} }