我正在尝试使用 NodeJS 启动到 Linux 机器的远程 ssh 会话。我的要求是在同一个 ssh 会话中依次运行多个 shell 命令。目前我正在使用
simple-ssh
npm 包,它可以工作,但我的要求是不依赖第三方工具/包。相反,建议我使用 child_process
进行远程 ssh 会话。
以下是使用
simple-ssh
包的示例:
var SSH = require("simple-ssh")
var ssh = new SSH({
host: 'remote_host',
user: 'username',
pass: 'password'
});
ssh.exec('pwd', {
out: console.log.bind(console)
})
.exec('ls', {
out: console.log.bind(console)
})
.exec('date', {
out: console.log.bind(console)
})
.start();
以下是我尝试过的
child_process
:
const { spawn } = require('child_process');
const child1 = spawn('sshpass', ['-p', 'password', 'ssh', '-T', '-o', 'StrictHostKeyChecking=no', `'username'@'remote_host'`]);
child1.stdin.write('password\n');
child1.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
child1.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child1.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
const child2 = spawn('sshpass', ['-p', 'password', 'ssh', '-T', '-o', 'StrictHostKeyChecking=no', `'username'@'remote_host'`]);
child2.stdin.write('password\n');
child2.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
child2.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child2.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
const child3 = spawn('sshpass', ['-p', 'password', 'ssh', '-T', '-o', 'StrictHostKeyChecking=no', `'username'@'remote_host'`]);
child3.stdin.write('password\n');
child3.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
child3.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child3.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
此外,如果我按顺序运行命令,然后打印输出,我会得到所有命令的单个输出(我不想要):
const { spawn } = require('child_process');
const child = spawn('sshpass', ['-p', 'password', 'ssh', '-T', '-o', 'StrictHostKeyChecking=no', `'username'@'remote_host'`]);
child.stdin.write('pwd\n');
child.stdin.write('ls -al\n');
child.stdin.write('date\n');
child.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
child.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
我的问题是如何在持久会话中将三个 ssh 命令与
child_process
spawn
链接起来,并一个接一个地异步运行命令?
注意:这并不完美,并且可能会在大输出时出现问题,例如文件传输或类似的节点流/缓冲区处理内容。但它应该可以胜任大多数场景,例如重新启动服务或执行一些命令。
我想指出的另一件事是,我无法让它与密码身份验证一起使用。目前这仅适用于公钥身份验证。
const { spawn } = require('child_process');
const { EOL } = require("os");
(async () => {
const queue = [];
let ssh = null;
function sleep(n) {
return new Promise((resolve) => {
setTimeout(resolve, n)
});
}
function init() {
return new Promise((resolve, reject) => {
// could not get it working with password
// use public key instead
let child = ssh = spawn("ssh", ["marc@localhost"]);
let message = (data) => {
// for feedback only
// the cmd response is returned from the send() function bewlow
console.log(`stdout/stderr > ${data}`);
if(queue.length === 0 ){
// init response
// motd/unread mails etc.
setTimeout(resolve, 1000);
}else{
// pop resolve from command queue
// return value should command response
// NOTE: "data" event could be fired more than once for larget outputs
// -> this would break this queue handling!
let done = queue.pop();
done(data);
}
};
child.once("spawn", () => {
console.log("ssh child command spawend");
// we do not care if writen to stdout or stderr
// handle both streams the same way
// if a command fails e.g. like the `cat /etc/groups` below
// a error messsage is writen to stderr, not stdout.
child.stderr.on("data", message);
child.stdout.on("data", message);
});
});
}
function close(){
return new Promise((resolve, reject) => {
ssh.once("exit", (code) => {
if(code === 0){
resolve();
}else{
reject(code)
}
});
ssh.stdin.write(`exit${EOL}`);
});
}
function send(cmd) {
return new Promise((resolve, reject) => {
ssh.stdin.write(`${cmd}${EOL}`, (err) => {
if (err) {
reject(err);
} else {
console.log(`# cmd "${cmd}" writen & queued - waiting for response`);
queue.unshift(resolve);
}
});
});
}
await init();
await sleep(1000);
await send("uptime")
await sleep(1000);
await send("cat /etc/groups"); // fails, becuase /etc/groups does not exists
await sleep(1000);
await send("cat /etc/group");
await sleep(1000);
await close();
})();
该程序会生成一个新的子 ssh 子进程,并包装 stdin/stdout 流辅助函数,例如“send”,它通过 ssh 将命令发送到您的服务器。
您的主要问题是您在向 ssh 子进程发送/通过管道传输下一个命令之前没有等待 ssh 子进程的响应。
我用一个(非常非常非常)简单的“队列”系统解决了这个问题。
再次强调,这段代码并不“完美”,很快就被组合在一起了。谨慎使用!
对于密码身份验证,我四处搜索并发现了几个类似的问题,但没有一个对我有用。 (例如 ssh 命令的
-tt
选项)。
ssh2 npm 包(simple-ssh 所依赖的)在 js 中实现了 ssh 协议,并使用网络套接字来传输命令。它不仅仅是 ssh 命令的包装。我虽然可以从那里复制“登录/密码解决方案”。
将“shell”选项设置为“true”以在 shell 内运行命令,默认情况下为“false”。您还可以查看node js子进程spawn命令文档
const ssh = spawn(
"sshpass",
[
"-p",
password,
"ssh",
"-T",
"-o",
"StrictHostKeyChecking=no",
`${username}@${host}`,
],
{ shell: true }
);