在child_process中持续运行SSH命令

问题描述 投票:0回答:2

我正在尝试使用 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
链接起来,并一个接一个地异步运行命令?

node.js ssh child-process
2个回答
0
投票

注意:这并不完美,并且可能会在大输出时出现问题,例如文件传输或类似的节点流/缓冲区处理内容。但它应该可以胜任大多数场景,例如重新启动服务或执行一些命令。

我想指出的另一件事是,我无法让它与密码身份验证一起使用。目前这仅适用于公钥身份验证。

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 命令的包装。我虽然可以从那里复制“登录/密码解决方案”。


0
投票

将“shell”选项设置为“true”以在 shell 内运行命令,默认情况下为“false”。您还可以查看node js子进程spawn命令文档

const ssh = spawn(
  "sshpass",
  [
    "-p",
    password,
    "ssh",
    "-T",
    "-o",
    "StrictHostKeyChecking=no",
    `${username}@${host}`,
  ],
  { shell: true }
);
© www.soinside.com 2019 - 2024. All rights reserved.