Java 和 SSH 希望从一个文件执行多个命令,跨越另一个文件中的主机列表并将输出写入文件
Java and SSH looking to execute multiple commands from a file, across a list of hosts in another file and write the output to a file
“my-thoughts”提供了下面的代码。但它并不是 100% 有效。 1) 如果我有多个命令,程序不会 运行。 2)命令文件中的一条命令,脚本无法正常执行。例如,使用“ls”,日志文件将具有以下 "Last login: Tue Jun 9 14:30:11 2015 from localhost lsmyhost:~ myaccount$ ls "
JSSH class:
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();
}
}
线路建设者Class:
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;
}
}
如果您正在使用 shell
通道并发送原始命令数据(即在您的情况下为 ByteArrayInputStream
),您需要确保在后面包含一个换行符 \n
每个命令。否则,命令将不会执行,只是坐等更多输入。
有关类似示例,请参阅我对 的回复。
每次调用 new FileOutputStream
时,都会覆盖 文件。所以你绝对不应该为每个命令都调用它,因为那样会导致一个文件只包含最后执行的命令的输出。
FileOutputStream 几乎总是包含在 BufferedOutputStream 中,因为它通常可以提高性能。很长一段时间以来都是如此,因为在 Java 甚至存在之前。
我猜您希望所有主机上的所有命令的输出都在该日志文件中。如果是这种情况,您希望在任何循环之外创建 OutputStream 一次。
您可能不应该为每个命令都创建一个新连接。将这两行移到内部 for 循环之外,这样它们就在创建频道之后:
channel.setOutputStream(outputStream);
channel.connect(15 * 1000);
请注意 documentation for setOutputStream 声明您应该在调用 Channel.connect() 之前调用它。此外,由于我们对来自所有主机的所有命令使用单个 OutputStream,因此您希望将 true
作为第二个参数传递,因此 JSch 不会关闭该 OutputStream。
事实上,documentation for setInputStream 说的是同一件事:它必须在调用 connect() 之前调用。
那么如何管理呢?您需要创建一个 "feeds" 通过管道连接到 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)
“my-thoughts”提供了下面的代码。但它并不是 100% 有效。 1) 如果我有多个命令,程序不会 运行。 2)命令文件中的一条命令,脚本无法正常执行。例如,使用“ls”,日志文件将具有以下 "Last login: Tue Jun 9 14:30:11 2015 from localhost lsmyhost:~ myaccount$ ls "
JSSH class:
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();
}
}
线路建设者Class:
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;
}
}
如果您正在使用 shell
通道并发送原始命令数据(即在您的情况下为 ByteArrayInputStream
),您需要确保在后面包含一个换行符 \n
每个命令。否则,命令将不会执行,只是坐等更多输入。
有关类似示例,请参阅我对
每次调用 new FileOutputStream
时,都会覆盖 文件。所以你绝对不应该为每个命令都调用它,因为那样会导致一个文件只包含最后执行的命令的输出。
FileOutputStream 几乎总是包含在 BufferedOutputStream 中,因为它通常可以提高性能。很长一段时间以来都是如此,因为在 Java 甚至存在之前。
我猜您希望所有主机上的所有命令的输出都在该日志文件中。如果是这种情况,您希望在任何循环之外创建 OutputStream 一次。
您可能不应该为每个命令都创建一个新连接。将这两行移到内部 for 循环之外,这样它们就在创建频道之后:
channel.setOutputStream(outputStream);
channel.connect(15 * 1000);
请注意 documentation for setOutputStream 声明您应该在调用 Channel.connect() 之前调用它。此外,由于我们对来自所有主机的所有命令使用单个 OutputStream,因此您希望将 true
作为第二个参数传递,因此 JSch 不会关闭该 OutputStream。
事实上,documentation for setInputStream 说的是同一件事:它必须在调用 connect() 之前调用。
那么如何管理呢?您需要创建一个 "feeds" 通过管道连接到 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)