Java 和 SSH 希望从一个文件执行多个命令,跨另一个文件中的主机列表并将输出写入文件

标签 java ssh

“my-thoughts”提供了下面的代码。但它并不是 100% 有效。 1)如果我有多个命令,程序将不会运行。 2)命令文件中的一条命令,脚本无法正常执行。例如,使用“ls”,日志文件将具有以下内容“上次登录:2015 年 6 月 9 日星期二 14:30:11 来自 localhost lsmyhost:~ myaccount$ ls”

JSSH 类:

public class JSSH {
    private static final String user = "UID"; 
    private static final String password = "pass";
    public static void main(String args[]) throws JSchException,
        InterruptedException, IOException {

        JSSH jssh = new JSSH();
        JSch jsch = new JSch();
        for (String host : jssh.listOfhost()) {
            Session session = jsch.getSession(user, host, 22);
            session.setPassword(password);
            session.setConfig(getProperties());
            session.connect(10 * 1000);
            Channel channel = session.openChannel("shell");

            for(String command : jssh.listOfCommand()) {
                channel.setInputStream(new ByteArrayInputStream(command.getBytes()));
                channel.setOutputStream(new FileOutputStream(new File(OUTPUT_FILE)));
                channel.connect(15 * 1000);
                TimeUnit.SECONDS.sleep(3);
            }

            channel.disconnect();
            session.disconnect();
        }
    }

    private static Properties getProperties() {
        Properties properties = new Properties();
        properties.put("StrictHostKeyChecking", "no");
        return properties;
    }

    private List<String> listOfCommand() throws IOException {
        return new LineBuilder("command_file.txt").build();
    }

    private List<String> listOfhost() throws IOException {
        return new LineBuilder("host_file.txt").build();
    }
}  

LineBuilder 类:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class LineBuilder {

    private String fileName;

    public LineBuilder(String fileName) {
        this.fileName = fileName;
    }

    public List<String> build() throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(new File(fileName)));
        List<String> lines = new ArrayList<String>();
        String line = null;
        try {
            while((line = reader.readLine()) != null) {
                lines.add(line);
            }
        } catch(IOException e) {
            throw e;
        } finally {
            reader.close();
        }
        return lines;
    }
}

最佳答案

每次调用 new FileOutputStream 时,您都在覆盖文件。所以你绝对不应该为每个命令调用它,因为这会导致一个文件只包含最后执行的命令的输出。

FileOutputStream 几乎总是应该包装在 BufferedOutputStream 中,因为它通常可以提高性能。很长一段时间以来都是如此,甚至在 Java 出现之前。

我猜您希望所有主机上的所有命令的输出都在该日志文件中。如果是这种情况,您希望在任何循环之外创建 OutputStream 一次

您可能不应该为每个命令都创建一个新连接。将这两行移到你的内部 for 循环之外,这样它们就在创建 channel 之后:

channel.setOutputStream(outputStream);
channel.connect(15 * 1000);

请注意 documentation for setOutputStream声明您应该在调用 Channel.connect() 之前调用它。此外,由于我们对来自所有主机的所有命令使用单个 OutputStream,因此您希望将 true 作为第二个参数传递,这样 JSch 就不会关闭该 OutputStream。

事实上,documentation for setInputStream说的是同一件事:必须在调用 connect() 之前调用它。

那么如何管理呢?您需要创建一个后台线程,通过管道将行“馈送”到 InputStream。这是通过 PipedInputStream 和 PipedOutputStream 实现的,它们允许一个线程从另一个线程提供的流中读取数据。

因此,修改后的版本可能如下所示:

JSSH jssh = new JSSH();
final String[] commands = jssh.listOfCommand();

// Local class for feeding commands to a pipe in a background thread.
class CommandSender
implements Runnable {
    private final OutputStream target;

    IOException exception;

    CommandSender(OutputStream target) {
        this.target = Objects.requireNonNull(target);
    }

    @Override
    public void run() {
        try {
            for (String command : commands) {
                target.write(command.getBytes());
                target.write(10);   // newline
                TimeUnit.SECONDS.sleep(3);
            }
        } catch (IOException e) {
            exception = e;
        } catch (InterruptedException e) {
            System.out.println("Interrupted, exiting prematurely.");
        }
    }
}

try (OutputStream log = new BufferedOutputStream(
    new FileOutputStream(OUTPUT_FILE))) {

    JSch jsch = new JSch();

    for (String host : jssh.listOfhost()) {
        Session session = jsch.getSession(user, host, 22);
        session.setPassword(password);
        session.setConfig(getProperties());
        session.connect(10 * 1000);

        Channel channel = session.openChannel("shell");
        channel.setOutputStream(log, true);

        try (PipedInputStream commandSource = new PipedInputStream();
             OutputStream commandSink = new PipedOutputStream(commandSource)) {

            CommandSender sender = new CommandSender(commandSink);
            Thread sendThread = new Thread(sender);
            sendThread.start();

            channel.setInputStream(commandSource);
            channel.connect(15 * 1000);

            sendThread.join();
            if (sender.exception != null) {
                throw sender.exception;
            }
        }

        channel.disconnect();
        session.disconnect();
    }
}

注意事项:调用 String.getBytes() 会使用平台的默认字符集将字符串的字符转换为字节。在 Windows 中,这通常是 UTF16-LE(每个字符两个字节),因此如果您从 Windows 到 Unix 或 Linux 机器建立 ssh 连接,这些机器通常需要以 UTF-8 编码的字符,您可能会遇到很多失败。简单的解决方案是指定一个显式字符集,假设您知道目标机器使用的字符集:

command.getBytes(StandardCharsets.UTF_8)

关于Java 和 SSH 希望从一个文件执行多个命令,跨另一个文件中的主机列表并将输出写入文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30805400/

相关文章:

java - J/Link 中 "if"语句的正确语法

java - 如何正确地将 avro 模式转换为 json 模式

ssh - 有没有办法从我的本地开发环境访问远程服务器上正在运行的docker容器(Sublime)

visual-studio-code - VS Code SSH 不断断开连接,但我可以正常使用 SSH

ruby - Rails 控制台无法在服务器上运行

ssh - 为什么 SSH 远程命令获得的环境变量比手动运行时少?

python - 使用 python 和 .pem 文件通过 SCP 进行文件传输的最佳方法

java - 如何设置TextView选中的文字背景颜色

java - 如何在 Spring-Shell 中屏蔽输入字符串

java - struts2检查权限拦截器