Fun experiment :P
This commit is contained in:
commit
2a01ad16a5
4 changed files with 94 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
4
Cargo.toml
Normal file
4
Cargo.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[package]
|
||||||
|
name = "ssh-shell-cmd-runner"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
A small experiment about running multiple shell commands over SSH using the same connection.
|
87
src/main.rs
Normal file
87
src/main.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
use std::{
|
||||||
|
io::{self, BufRead, BufReader, Write},
|
||||||
|
process::{exit, Child, ChildStderr, ChildStdin, ChildStdout, Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Runner {
|
||||||
|
stdin: ChildStdin,
|
||||||
|
stdout: BufReader<ChildStdout>,
|
||||||
|
stderr: BufReader<ChildStderr>,
|
||||||
|
process: Child,
|
||||||
|
line_buf: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print every line of `reader` until the end or until the line "CMD_DONE" is found.
|
||||||
|
fn print_until_cmd_done(mut reader: impl BufRead, buf: &mut String) -> io::Result<()> {
|
||||||
|
loop {
|
||||||
|
buf.clear();
|
||||||
|
let n = reader.read_line(buf)?;
|
||||||
|
if n == 0 || buf.as_bytes() == b"CMD_DONE\n" {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
print!("{buf}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runner {
|
||||||
|
fn build(host: &str) -> io::Result<Self> {
|
||||||
|
let mut process = Command::new("ssh")
|
||||||
|
.arg(host)
|
||||||
|
.arg("bash")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
stdin: process.stdin.take().unwrap(),
|
||||||
|
stdout: BufReader::new(process.stdout.take().unwrap()),
|
||||||
|
stderr: BufReader::new(process.stderr.take().unwrap()),
|
||||||
|
process,
|
||||||
|
line_buf: String::with_capacity(1024),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// WARNING: Command injection is possible! Don't use it with untrusted input!
|
||||||
|
fn run_shell_command(&mut self, cmd: &str) -> io::Result<()> {
|
||||||
|
println!("\n\nRunning `{cmd}`");
|
||||||
|
|
||||||
|
writeln!(self.stdin, "{cmd}")?;
|
||||||
|
// Send CMD_DONE to stdout and stderr to be able to know when the output of a command ends.
|
||||||
|
writeln!(self.stdin, "echo CMD_DONE")?;
|
||||||
|
writeln!(self.stdin, "echo CMD_DONE >&2")?;
|
||||||
|
|
||||||
|
println!("stdout:");
|
||||||
|
print_until_cmd_done(&mut self.stdout, &mut self.line_buf)?;
|
||||||
|
|
||||||
|
println!("\nstderr:");
|
||||||
|
print_until_cmd_done(&mut self.stderr, &mut self.line_buf)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Runner {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.run_shell_command("exit").unwrap();
|
||||||
|
self.process.wait().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
// ENTER THE HOST
|
||||||
|
let host = "";
|
||||||
|
|
||||||
|
if host.is_empty() {
|
||||||
|
eprintln!("No host specified!");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut runner = Runner::build(host)?;
|
||||||
|
|
||||||
|
runner.run_shell_command("ls -al")?;
|
||||||
|
runner.run_shell_command("cd .config")?;
|
||||||
|
runner.run_shell_command("ls -al")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Reference in a new issue