rustlibssh2ssh2

How can I run a sequence of commands using ssh2-rs?


use ssh2::Session;
use std::io::prelude::*;
use std::net::TcpStream;

fn main() {
    // Connect to the local SSH server
    let tcp = TcpStream::connect("test.rebex.net:22").unwrap();
    let mut sess = Session::new().unwrap();
    sess.set_tcp_stream(tcp);
    sess.handshake().unwrap();
    sess.userauth_password("demo", "password").unwrap();

    let commands = ["cd /pub/example/","ls"];
    let mut channel = sess.channel_session().unwrap();
    let mut s = String::new();
    
    for command in commands {
        println!("{}", command);    
        channel.exec(command).unwrap();    
        channel.read_to_string(&mut s).unwrap();
        println!("{}", s);
    }

    channel.wait_close();
    println!("{}", channel.exit_status().unwrap());
}

For some reason I get the below error:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { code: Session(-39), msg: "Channel can not be reused" }', src\main.rs:19:31

I have few other questions, but not sure they can fit in the same post:

How can I run multiple commands based on the output of previous commands?

How to make this code work so I can automatically execute a sequence of commands? (Ex: change the password for a given user)


Solution

  • If you want to execute more than one command, you need to rely on the shell of the host.

    channel.shell().unwrap();
    for command in commands {
        channel.write_all(command.as_bytes()).unwrap();
        channel.write_all(b"\n").unwrap();
    } // Bit inefficient to use separate write calls
    channel.send_eof().unwrap();
    println!("Waiting for output");
    channel.read_to_string(&mut s).unwrap();
    println!("{}", s);
    

    However, how to chain several commands in a shell is up to that shell. If, for example, the shell on your target host is a sh/bash/zsh/ash/fish/… and you don't want subsequent commands to be executed if one command fails (and have a reliable exit code), then && might be a better choice (but you could also start with set -e). But if you're doing that, you might as well use exec.

    channel.exec(&commands.join(" && ")).unwrap();
    channel.read_to_string(&mut s).unwrap();