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