How to send input to a program through stdin in Rust - shell

I am attempting to write a shell in Rust. One of the functions of a shell is being able to redirect input to a file, redirect a file to input, and pipe output of a program into another program. I am using the run::process_output functions in std to run programs and get their output, but I don't know how to send input as if it was stdin to a program after running it. Is there some way to create an object that is directly connected to the ran program and push in input like it was typed in stdin?

This program demonstrates how you can launch external programs and stream their stdout -> stdin together:
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::process::{Command, Stdio};
fn main() {
// Create some argument vectors for lanuching external programs
let a = vec!["view", "-h", "file.bam"];
let outsam = vec!["view", "-bh", "-o", "rust.bam", "-"];
let mut child = Command::new("samtools")
.args(&a)
.stdout(Stdio::piped())
.spawn()
.unwrap();
let outchild = Command::new("samtools")
.args(&outsam)
.stdin(Stdio::piped())
.spawn()
.unwrap();
// Create a handle and writer for the stdin of the second process
let mut outstdin = outchild.stdin.unwrap();
let mut writer = BufWriter::new(&mut outstdin);
// Loop over the output from the first process
if let Some(ref mut stdout) = child.stdout {
for line in BufReader::new(stdout).lines() {
let mut l: String = line.unwrap();
// Need to add an end of line character back to the string
let eol: &str = "\n";
l = l + eol;
// Print some select lines from the first child to stdin of second
if (l.chars().skip(0).next().unwrap()) == '#' {
// convert the string into bytes and write to second process
let bytestring = l.as_bytes();
writer.write_all(bytestring).unwrap();
}
}
}
}

You'll need a handle to a running process to do this.
// spawn process
let mut p = std::process::Command::new(prog).arg(arg).spawn().unwrap();
// give that process some input, processes love input
p.stdin.as_mut().unwrap().write_str(contents);
// wait for it to complete, you may need to explicitly close stdin above
// i.e. p.stdin.as_mut().unwrap().close();
p.wait();
The above should let you send arbitrary input to a process. It would be important to close the stdin pipe if the spawned process reads until eof, like many programs do.

An updated version of Michael's answer. If your output/input is small, you can read it into a string and pipe it back in the following manner:
let output = Command::new("ls").arg("-aFl")
.output().unwrap().stdout;
let output = String::from_utf8_lossy(&output);
println!("First program output: {:?}", output);
let put_command = Command::new("my_other_program")
.stdin(Stdio::piped())
.spawn().unwrap();
write!(put_command.stdin.unwrap(), "{}", output).unwrap();

Related

How to wait for the last cmd command to generate a file, and then read the binary of this file, using Rust

I am writing a program, the desired effect is to generate a lua file, then compile this lua on windows platform using luac.exe and read the binary value of the generated luac.out file.
My code is here
fn test(lua_path: PathBuf, luac_path: String) -> Result<String, Box<dyn Error>> {
let index = luac_path.rfind("/").ok_or("path fail")?;
let (path, file) = luac_path.split_at(index + 1);
let cmd_str = format!("/c cd /d {} && .\\{} {}", path, file, lua_path.display());
let child = Command::new("cmd").raw_arg(cmd_str).spawn()?;
let output = child.wait_with_output()?;
if !output.status.success() {
let output_str = String::from_utf8(output.stderr)?;
return Err(output_str.into());
}
// Without the following code, luac.out will be generated normally.
// But once the following code is added, luac.out will be generated as 0kb and receive an access denied error
let out_path = Path::new(&path).join("luac.out");
let mut out_file = File::create(&out_path)?;
let mut buf = Vec::new();
out_file.read_to_end(&mut buf)?;
let result = String::from_utf8_lossy(&buf).to_string();
Ok(result)
}
My guess is that after executing child.wait, the file is not created to completion and then the program accesses it causing an error, how do I fix this?

How to read one single char in Rust? [duplicate]

I want to run an executable that blocks on stdin and when a key is pressed that same character is printed immediately without Enter having to be pressed.
How can I read one character from stdin without having to hit Enter? I started with this example:
fn main() {
println!("Type something!");
let mut line = String::new();
let input = std::io::stdin().read_line(&mut line).expect("Failed to read line");
println!("{}", input);
}
I looked through the API and tried replacing read_line() with bytes(), but everything I try requires me to hit Enter before read occurs.
This question was asked for C/C++, but there seems to be no standard way to do it: Capture characters from standard input without waiting for enter to be pressed
It might not be doable in Rust considering it's not simple in C/C++.
While #Jon's solution using ncurses works, ncurses clears the screen by design. I came up with this solution that uses the termios crate for my little project to learn Rust. The idea is to modify ECHO and ICANON flags by accessing tcsetattr through termios bindings.
extern crate termios;
use std::io;
use std::io::Read;
use std::io::Write;
use termios::{Termios, TCSANOW, ECHO, ICANON, tcsetattr};
fn main() {
let stdin = 0; // couldn't get std::os::unix::io::FromRawFd to work
// on /dev/stdin or /dev/tty
let termios = Termios::from_fd(stdin).unwrap();
let mut new_termios = termios.clone(); // make a mutable copy of termios
// that we will modify
new_termios.c_lflag &= !(ICANON | ECHO); // no echo and canonical mode
tcsetattr(stdin, TCSANOW, &mut new_termios).unwrap();
let stdout = io::stdout();
let mut reader = io::stdin();
let mut buffer = [0;1]; // read exactly one byte
print!("Hit a key! ");
stdout.lock().flush().unwrap();
reader.read_exact(&mut buffer).unwrap();
println!("You have hit: {:?}", buffer);
tcsetattr(stdin, TCSANOW, & termios).unwrap(); // reset the stdin to
// original termios data
}
One advantage of reading a single byte is capturing arrow keys, ctrl etc. Extended F-keys are not captured (although ncurses can capture these).
This solution is intended for UNIX-like platforms. I have no experience with Windows, but according to this forum perhaps something similar can be achieved using SetConsoleMode in Windows.
Use one of the 'ncurses' libraries now available, for instance this one.
Add the dependency in Cargo
[dependencies]
ncurses = "5.86.0"
and include in main.rs:
extern crate ncurses;
use ncurses::*; // watch for globs
Follow the examples in the library to initialize ncurses and wait for single character input like this:
initscr();
/* Print to the back buffer. */
printw("Hello, world!");
/* Update the screen. */
refresh();
/* Wait for a key press. */
getch();
/* Terminate ncurses. */
endwin();
You can also use termion, but you will have to enable the raw TTY mode which changes the behavior of stdout as well. See the example below (tested with Rust 1.34.0). Note that internally, it also wraps the termios UNIX API.
Cargo.toml
[dependencies]
termion = "1.5.2"
main.rs
use std::io;
use std::io::Write;
use std::thread;
use std::time;
use termion;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
fn main() {
// Set terminal to raw mode to allow reading stdin one key at a time
let mut stdout = io::stdout().into_raw_mode().unwrap();
// Use asynchronous stdin
let mut stdin = termion::async_stdin().keys();
loop {
// Read input (if any)
let input = stdin.next();
// If a key was pressed
if let Some(Ok(key)) = input {
match key {
// Exit if 'q' is pressed
termion::event::Key::Char('q') => break,
// Else print the pressed key
_ => {
write!(
stdout,
"{}{}Key pressed: {:?}",
termion::clear::All,
termion::cursor::Goto(1, 1),
key
)
.unwrap();
stdout.lock().flush().unwrap();
}
}
}
thread::sleep(time::Duration::from_millis(50));
}
}
Here's a lightweight solution only using the libc crate based some code from the console crate:
fn setup_raw_terminal() -> io::Result<()> {
unsafe {
let tty;
let fd = if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
tty = fs::File::open("/dev/tty")?;
tty.as_raw_fd()
};
let mut ptr = core::mem::MaybeUninit::uninit();
if libc::tcgetattr(fd, ptr.as_mut_ptr()) == 0 {
let mut termios = ptr.assume_init();
let c_oflag = termios.c_oflag;
libc::cfmakeraw(&mut termios);
termios.c_oflag = c_oflag;
if libc::tcsetattr(fd, libc::TCSADRAIN, &termios) == 0 {
return Ok(());
}
}
}
Err(io::Error::last_os_error())
}
It needs to be called before reading stdin:
let mut buf = [0u8; 1024];
let mut stdin = io::stdin();
setup_raw_terminal()?;
loop {
let size = stdin.read(&mut buf)?;
let data = &buf[0..size];
println!("stdin data: {}", data);
}

How to terminate all threads reading from Pipe (NSPipe) related to Process (NSTask)?

I am writing a MacOS/Cocoa app that monitors a remote log file using a common recipe that launches a Process (formerly NSTask) instance on a background thread and reads stdout of the process via a Pipe (formally a NSPipe) as listed below:
class LogTail {
var process : Process? = nil
func dolog() {
//
// Run ssh fred#foo.org /usr/bin/tail -f /var/log.system.log
// on a background thread and monitor it's stdout.
//
let processQueue = DispatchQueue.global(qos: .background)
processQueue.async {
//
// Create process and associated command.
//
self.process = Process()
process.launchPath = "/usr/bin/ssh"
process.arguments = ["fred#foo.org",
"/usr/bin/tail", "-f",
"/var/log.system.log"]
process.environment = [ ... ]
//
// Create pipe to read stdout of command as data is available
//
let pipe = Pipe()
process.standardOutput = pipe
let outHandle = pipe.fileHandleForReading
outHandle.readabilityHandler = { pipe in
if let string = String(data: pipe.availableData,
encoding: .utf8) {
// write string to NSTextView on main thread
}
}
//
// Launch process and block background thread
// until process complete.
//
process.launch()
process.waitUntilExit()
//
// What do I do here to make sure all related
// threads terminate?
//
outHandle.closeFile() // XXX
outHandle.readabilityHandler = nil // XXX
}
}
Everything works just dandy, but when the process quits (killed via process.terminate) I notice (via Xcode's Debug Navigator and the Console app) that there are multiple threads consuming 180% or more of the CPU!?!
Where is this CPU leak coming from?
I threw in outHandle.closeFile() (see code marked XXX above) and that reduced the CPU usage down to just a few percent but the threads still existed! What am I doing wrong or how do a make sure all the related threads terminate (I prefer graceful terminations i.e., threads body finish executing)!?
Some one posted a similar question almost 5 years ago!
UPDATE:
The documentation for NSFileHandle's readabilityHandler says:
To stop reading the file or socket, set the value of this property to
nil. Doing so cancels the dispatch source and cleans up the file
handle’s structures appropriately.
so setting outHandle.readabilityHandler = nil seems to solve the problem too.
Even though I have seemingly solved the problem, I really don't understand where this massive CPU leak comes from -- very mysterious.

lifetime not long enough rust

I want to open a file, replace some characters, and make some splits. Then I want to return the list of strings. however I get error: broken does not live long enough. My code works when it is in main, so it is only an issue with lifetimes.
fn tokenize<'r>(fp: &'r str) -> Vec<&'r str> {
let data = match File::open(&Path::new(fp)).read_to_string(){
Ok(n) => n,
Err(e) => fail!("couldn't read file: {}", e.desc)
};
let broken = data.replace("'", " ' ").replace("\"", " \" ").replace(" ", " ");
let mut tokens = vec![];
for t in broken.as_slice().split_str(" ").filter(|&x| *x != "\n"){
tokens.push(t)
}
return tokens;
}
How can I make the value returned by this function live in the scope of the caller?
The problem is that your function signature says "the result has the same lifetime as the input fp", but that's simply not true. The result contains references to data, which is allocated inside your function; it has nothing to do with fp! As it stands, data will cease to exist at the end of your function.
Because you're effectively creating new values, you can't return references; you need to transfer ownership of that data out of the function. There are two ways I can think of to do this, off the top of my head:
Instead of returning Vec<&str>, return Vec<String>, where each token is a freshly-allocated string.
Return data inside a wrapper type which implements the splitting logic. Then, you can have fn get_tokens(&self) -> Vec<&str>; the lifetime of the slices can be tied to the lifetime of the object which contains data.

D: executeShell on Windows to run another program not returning immediately

I'm using D as a scripting language for Windows 7 console stuff to automate boring tasks. One of my scripts (open.exe) is supposed to allow me to open stuff from the command line without me having to specify which program I use (I have a configuration file with this stuff). Now, I use executeShell to do this, and call something like start [name of program I want to use] [name of input file]. If I do this directly from the shell, it returns immediately, but if I do it using my D script, it doesn't return until the program that it opens is closed. What should I do to allow it to return immediately?
For reference purposes, this is the business logic of my script (the main method just does some argument parsing for piping purposes):
immutable path = "some//path//going//to//config//file.conf";
void process(string input) {
string extension = split(input,".")[1]; //get file extension from input
auto config = File(path,"r"); auto found = false;
while (!config.eof()){
auto line = chomp(config.readln());
if (line[0]!='#') { //skip comment lines
auto divided = split(line, ":");
if (divided[0] == extension) {
found = true;
auto command = "start " ~ divided[1] ~ " " ~ input;
auto result = executeShell(command);
//test for error code and output if necessary
writeln(result.output);
}
}
}
if (!found)
writeln("ERROR: Don't know how to open " ~ input);
}
From the top of the std.process documentation:
Execute and wait for completion, collect output - executeShell
The Windows start program spawns a process and exits immediately. D's executeShell does something else. If you'd like to spawn another program, use the appropriate functions: spawnProcess or spawnShell.

Resources