use command_stream::{OutputChunk, ProcessRunner, RunOptions, StreamingRunner};
const SLEEP: &str = "/bin/sleep";
fn quiet() -> RunOptions {
RunOptions {
mirror: false,
capture: true,
..Default::default()
}
}
fn ps(format: &str, pid: u32) -> String {
let output = std::process::Command::new("ps")
.args(["-o", format, "-p", &pid.to_string()])
.output()
.expect("ps is available");
String::from_utf8_lossy(&output.stdout).trim().to_string()
}
fn children(pid: u32) -> Vec<String> {
let output = std::process::Command::new("pgrep")
.args(["-P", &pid.to_string()])
.output()
.expect("pgrep is available");
String::from_utf8_lossy(&output.stdout)
.split_whitespace()
.map(str::to_string)
.collect()
}
fn is_alive(pid: u32) -> bool {
std::process::Command::new("ps")
.args(["-p", &pid.to_string()])
.output()
.expect("ps is available")
.status
.success()
}
#[tokio::main]
async fn main() -> command_stream::Result<()> {
println!("=== 1. Before, during and after the command ===");
let mut worker = ProcessRunner::new(format!("{SLEEP} 5"), quiet());
println!("before start: {:?}", worker.pid());
worker.start().await?;
println!("while running: {:?}", worker.pid());
worker.kill()?;
let _ = worker.run().await;
println!("after exit: {:?}", worker.pid());
println!("\n=== 2. After a plain run ===");
let mut done = ProcessRunner::new("sh -c 'echo done'", quiet());
done.run().await?;
println!("pid: {:?}", done.pid());
println!("\n=== 3. Built-in commands have no process id ===");
let mut builtin = ProcessRunner::new("echo hello", quiet());
builtin.run().await?;
println!("built-in echo: {:?}", builtin.pid());
let mut external = ProcessRunner::new("/bin/echo hello", quiet());
external.run().await?;
println!("/bin/echo instead: {:?}", external.pid());
println!("\n=== 4. What the id names ===");
let mut shell_run = ProcessRunner::new(format!("{SLEEP} 30"), quiet());
shell_run.start().await?;
let shell_pid = shell_run.pid().expect("a spawned command has a pid");
println!("pid {shell_pid} is: {}", ps("args=", shell_pid));
println!(
"its process group: {} (same as the pid)",
ps("pgid=", shell_pid)
);
let shell_children = children(shell_pid);
for child in &shell_children {
let child_pid: u32 = child.parse().expect("pgrep prints ids");
println!(" child {child_pid}: {}", ps("args=", child_pid));
}
if shell_children.is_empty() {
println!(" no children - this shell replaced itself with the command");
}
shell_run.kill()?;
let _ = shell_run.run().await;
println!("\n=== 5. Streaming commands ===");
let mut stream = StreamingRunner::new(format!("{SLEEP} 30")).stream();
println!("immediately after stream(): {:?}", stream.pid());
let streamed_pid = stream.wait_for_pid().await.expect("the child was spawned");
println!("after wait_for_pid(): {streamed_pid}");
println!("which is: {}", ps("args=", streamed_pid));
stream.kill();
while let Some(chunk) = stream.next().await {
if let OutputChunk::Exit(code) = chunk {
println!("exit code: {code}");
}
}
println!("\n=== 6. Checking whether the command is still running ===");
let mut short_lived = ProcessRunner::new(format!("{SLEEP} 0.3"), quiet());
short_lived.start().await?;
let short_pid = short_lived.pid().expect("a spawned command has a pid");
println!("running: {}", is_alive(short_pid));
short_lived.run().await?;
println!("finished: {}", is_alive(short_pid));
Ok(())
}