如何用Node.js的child_process持久化SSH会话并异步执行多命令?
问题描述
我需要通过Node.js的child_process模块建立与Linux机器的持久化远程SSH会话,在同一个会话中异步依次执行多个Shell命令。之前用simple-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();
我的尝试及问题
尝试1:多次创建SSH会话
每次执行命令都新建一个SSH进程,虽然能分开输出,但会建立多个连接,不符合持久化会话的要求:
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}`); });
尝试2:一次性发送所有命令
在同一个会话里连续发送命令,但所有命令的输出会混在一起,无法区分每个命令的结果:
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}`); });
核心问题:怎么用child_process.spawn在同一个持久化SSH会话中,异步依次执行多个命令,并且能分别获取每个命令的输出?
解决方案
要实现这个需求,关键是监听前一个命令的执行完成信号,再发送下一个命令,同时给每个命令的输出做标识,方便区分结果。
实现思路
- 启动单个SSH会话进程,保持连接持久化;
- 维护待执行命令的队列;
- 监听SSH进程输出,通过远程Shell的提示符(如
$或#)判断前一个命令是否执行完成; - 完成一个命令后,从队列取出下一个命令发送;
- 单独收集并打印每个命令的输出,避免混淆。
完整代码示例
const { spawn } = require('child_process'); // SSH配置信息 const sshConfig = { host: 'remote_host', username: 'username', password: 'password' }; // 待执行的命令队列 const commands = ['pwd', 'ls', 'date']; let currentCmdIndex = 0; let currentCmdOutput = ''; // 启动持久化SSH会话 const sshProcess = spawn('sshpass', [ '-p', sshConfig.password, 'ssh', '-T', '-o', 'StrictHostKeyChecking=no', `${sshConfig.username}@${sshConfig.host}` ]); // 处理标准输出,判断命令执行状态并收集输出 sshProcess.stdout.on('data', (data) => { const output = data.toString(); // 收集当前命令的输出(排除Shell提示符) if (!output.includes('$') && !output.includes('#')) { currentCmdOutput += output; } // 检测到提示符,说明当前命令执行完成 if (output.endsWith('$ ') || output.endsWith('# ')) { if (currentCmdIndex < commands.length) { // 打印当前命令的结果 console.log(`=== 命令 "${commands[currentCmdIndex]}" 的输出 ===`); console.log(currentCmdOutput.trim()); console.log('=====================================\n'); // 重置输出缓冲区,准备执行下一个命令 currentCmdOutput = ''; currentCmdIndex++; // 发送下一个命令,所有命令执行完后关闭会话 if (currentCmdIndex < commands.length) { sshProcess.stdin.write(`${commands[currentCmdIndex]}\n`); } else { sshProcess.stdin.write('exit\n'); } } } }); // 监听错误输出 sshProcess.stderr.on('data', (data) => { console.error(`SSH错误输出: ${data.toString()}`); }); // 会话关闭事件 sshProcess.on('close', (code) => { console.log(`SSH会话已关闭,退出码: ${code}`); }); // 发送第一个命令启动执行流程 sshProcess.stdin.write(`${commands[currentCmdIndex]}\n`);
关键说明
- 依赖远程Shell的提示符判断命令执行完成,不同系统或自定义提示符需调整判断逻辑;
- 命令按队列顺序执行,前一个命令完成后才会发送下一个,实现异步依次执行;
- 每个命令的输出单独收集和打印,避免混在一起;
- 所有命令执行完毕后自动发送
exit关闭SSH会话。
注意事项
- 确保本地已安装
sshpass(密码登录场景),密钥登录可去掉sshpass参数,改用-i指定密钥文件; - 若远程Shell有自定义提示符,需修改代码中提示符的判断规则;
- 可添加超时逻辑,防止单个命令卡住导致会话挂起。
内容的提问来源于stack exchange,提问作者samnoon
相关产品推荐
相关产品推荐

