해결책
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);
}
다른 팁
이것은 뻔뻔한 플러그이지만 지금은 글쓰기 광범위한 JSCH를위한 Javadoc.
또한 이제 a 수동 JSCH Wiki에서 (주로 저에 의해 작성).
원래 질문에 대해, 실제로 스트림을 처리하는 예는 없습니다. 스트림 읽기/쓰기는 언제나처럼 이루어집니다.
그러나 쉘의 한 명령이 쉘의 출력을 읽는 것만으로 완료되었을 때 (SSH 프로토콜과 무관 한) 알 수있는 확실한 방법이있을 수 없습니다.
쉘이 대화식 인 경우, 즉 터미널이 부착되어 있으면 일반적으로 프롬프트가 인쇄되어 인식하려고합니다. 그러나 적어도 이론적 으로이 프롬프트 문자열은 명령의 정상 출력에서도 발생할 수 있습니다. 확실히 원한다면 개인을 열어주세요 exec
쉘 채널을 사용하는 대신 각 명령의 채널. 쉘 채널은 주로 인간 사용자가 대화식 사용에 사용됩니다.
나는 INPUT 스트림으로 System을 사용하지 않고 JSCH가 작동하지 않기 위해 반나절 동안 고군분투했습니다. 나는 Ganymed를 시도했다 http://www.ganymed.ch/ssh2/ 그리고 5 분 안에 갔다. 모든 예제는 앱의 하나의 사용을 목표로하는 것처럼 보이며 예제 중 어느 것도 내가 필요한 것을 보여주지 않았습니다. Ganymed의 예제 Basic.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;
}
}
}
Java에서 SSH를 사용하는 것은 JSCH만큼 어렵지 않아야합니다. 당신은 더 나아질 수 있습니다 SSHJ.
그만큼 거친 터미널은 JSCH를 사용하도록 작성되었지만 더 나은 취급 및 VT102 에뮬레이션으로 작성되었습니다. 코드를 볼 수 있습니다. 우리는 그것을 사용하고 그것은 잘 작동합니다.
나는 2000 년 이래로 JSCH를 사용하고 있으며 여전히 사용하기에 좋은 라이브러리라고 생각합니다. 나는 그것이 충분히 문서화되지 않았지만 제공된 예제는 몇 분 안에 필요한 것을 이해하기에 충분히 좋아 보이며, 사용자 친화적 인 스윙은 매우 독창적 인 접근 방식이지만 예제를 빠르게 테스트하여 실제로 작동하는지 확인할 수 있습니다. 모든 좋은 프로젝트가 작성된 코드의 양보다 3 배 더 많은 문서가 필요하다는 것은 항상 사실이 아니며, 그러한 존재가 존재하는 경우에도 항상 개념의 작업 프로토 타입을 더 빨리 쓰는 데 도움이되지는 않습니다.
자선 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) {}
}