首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java和SSH希望通过另一个文件中的主机列表从一个文件执行多个命令,并将输出写到一个文件中

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

Stack Overflow用户
提问于 2015-06-12 14:23:08
回答 2查看 1.9K关注 0票数 1

“我的想法”提供了下面的代码。但它不能百分之百地发挥作用。1)如果我有多个命令,程序就不能运行。2)命令文件中的一个命令,脚本将无法正确执行。例如,对于“ls”,日志文件将具有以下内容:“最后登录: Tue Jun 9 14:30:11 2015从localhost lsmyhost:~ myaccount$ ls”

JSSH类:

代码语言:javascript
复制
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类:

代码语言:javascript
复制
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;
    }
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-06-12 18:23:25

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

FileOutputStream几乎总是被包装在BufferedOutputStream中,因为它经常提高性能。这是很长一段时间以来的事了,因为在Java之前就已经存在了。

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

您可能不应该为每个命令创建一个新的连接。将这两条线移出您的内部for-循环,因此它们就在创建通道之后:

代码语言:javascript
复制
channel.setOutputStream(outputStream);
channel.connect(15 * 1000);

注意,setOutputStream文档声明在调用Channel.connect()之前应该调用它。另外,由于我们对来自所有主机的所有命令都使用了一个OutputStream,所以您希望传递true作为第二个参数,因此JSch不会关闭该OutputStream。

实际上,setInputStream文档也是这样说的:在调用connect()之前必须调用它。

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

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

代码语言:javascript
复制
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中,这通常是UTF 16-LE(每个字符有两个字节),因此,如果要从Windows到Unix或Linux机器进行ssh连接,而这些机器通常需要使用UTF-8编码的字符,则可能会出现很多故障。简单的解决方案是指定一个显式字符集,假设您知道目标机器使用的字符集:

代码语言:javascript
复制
command.getBytes(StandardCharsets.UTF_8)
票数 3
EN

Stack Overflow用户

发布于 2015-06-12 16:22:00

如果您正在使用shell通道并发送原始命令数据(在您的情况下是一个ByteArrayInputStream ),则需要确保在每个命令之后包含一个换行符\n。否则,命令将不会执行,而只是坐着等待更多的输入。

有关类似的示例,请参阅我对这个问题的响应。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30805400

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档