use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem};
use std::io::{Read, Write};
use std::sync::mpsc::{channel, Sender};
use std::thread;
fn main() {
let pty_system = NativePtySystem::default();
// Open the PTY
let pair = pty_system
.openpty(PtySize {
rows: 24,
cols: 80,
pixel_width: 0,
pixel_height: 0,
})
.unwrap();
let mut cmd = CommandBuilder::new("bash");
cmd.arg("-i");
let mut child = pair.slave.spawn_command(cmd).unwrap();
drop(pair.slave);
// Create channels
let (tx_input, rx_input) = channel::<String>(); // For sending input
let (tx_output, rx_output) = channel::<String>(); // For receiving output
let mut reader = pair.master.try_clone_reader().unwrap();
let master_writer = pair.master.take_writer().unwrap();
// Spawn a thread to read PTY output and send it to main
thread::spawn(move || {
handle_output_stream(reader, tx_output);
});
let tx_writer = thread::spawn(move || {
handle_input_stream(rx_input, master_writer);
});
println!("You can now type commands for Bash (type 'exit' to quit):");
let mut stored_output = String::new(); // Store output in main
loop {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
if input.trim() == "exit" {
break;
}
tx_input.send(input.clone()).unwrap();
// Collect the output received from the reader thread
while let Ok(output) = rx_output.try_recv() {
stored_output.push_str(&output);
print!("{}", output); // i get no output without this line
} }
drop(tx_input);
tx_writer.join().unwrap();
println!("Waiting for Bash to exit...");
let status = child.wait().unwrap();
println!("Bash exited with status: {:?}", status);
// Show stored output
println!("\n===== Stored PTY Output =====\n{}", stored_output); // this line is not printed
}
fn handle_output_stream(mut reader: Box<dyn Read + Send>, tx_output: Sender<String>) {
let mut buffer = [0u8; 1024];
let mut collected_output = String::new();
loop {
match reader.read(&mut buffer) {
Ok(0) => break,
Ok(n) => {
let output = String::from_utf8_lossy(&buffer[..n]).to_string();
collected_output.push_str(&output);
tx_output.send(output).unwrap(); // Send to main thread
}
Err(e) => {
eprintln!("Error reading from PTY: {}", e);
break;
}
}
}
}
fn handle_input_stream(rx: std::sync::mpsc::Receiver<String>, mut writer: Box<dyn Write + Send>) {
for input in rx.iter() {
if writer.write_all(input.as_bytes()).is_err() {
eprintln!("Error writing to PTY");
break;
}
}
}
I am trying to pass input to this pty implementation using portable_pty crate and capture only the output from the pty but instead i get a shell which is not what i want, I am a beginner in Rust and I do not understand why this happens.
I know I could do this with spawning a command using std::process::Command but that is not what i want, what i want is a persistent shell, how do i capture only the commands from the pty ?