Read lines from std input using Rust - rust

Hi I want to be able to read a file which contains json lines into a rust app like this
$ cargo run < users.json
and then read those lines as an iterator. As of now I have this code but i don't want the file hard coded but piped into the process as in the line above.
use std::fs::File;
use std::io::{self, prelude::*, BufReader};
fn main() -> io::Result<()> {
let file = File::open("users.json")?;
let reader = BufReader::new(file);
for line in reader.lines() {
println!("{}", line);
}
Ok(())
}
I just solved it this makes the trick
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
println!("{}", line.unwrap());
}
}

cargo help run reveals:
NAME
cargo-run - Run the current package
SYNOPSIS
cargo run [options] [-- args]
DESCRIPTION
Run a binary or example of the local package.
All the arguments following the two dashes (--) are passed to the binary to run. If you're passing arguments to both Cargo and the binary, the ones after -- go to the binary, the ones before go to Cargo.
So to pass arguments the syntax would be:
cargo run -- foo bar baz
You can then access the values like this:
let args: Vec<String> = env::args().collect();
A complete minimal example would be:
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
dbg!(&args);
}
Running cargo run -- users.json would result in:
$ cargo run -- users.json
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/sandbox users.json`
[src/main.rs:5] &args = [
"target/debug/sandbox",
"users.json",
]

use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
println!("{}", line.unwrap());
}
}

Related

Why the env_logger doesn't work in my cli program

In Cargo.toml, I write this:
log = "0.4.0"
env_logger = "0.8.4"
In my test code, I write this:
use log::{info, warn};
fn main() {
env_logger::init();
info!("starting up");
warn!("oops, nothing done");
}
And then I set the env like this:
$ env | grep RUST
RUST_LOG=output_log=info
Every thing was done, and the test code worked well:
$ cargo run --bin output-log
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/output-log`
[2021-11-06T04:05:42Z INFO output_log] starting up
[2021-11-06T04:05:42Z WARN output_log] oops, nothing done
But When I did this in my program:
use log::*;
use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::path::PathBuf;
...
fn main() -> Result<()> {
let stdout = io::stdout();
let mut stdout = BufWriter::new(stdout);
env_logger::init();
info!("This is info");
warn!("This is warn");
error!("This is error");
...
// write something to the stdout and flush
...
Ok(())
}
Then, the env_logger didn't print any message to the terminal as before
Why this happens and what should I do to fix it?
export RUST_LOG=`executable_name`=`log_level`,...
So I should write
export RUST_LOG=grrs=info

rustc false positive warning about value never read?

Following this article about writing a shell in Rust. Compiler outputs a warning about a variable never being read.
use std::io::stdin;
use std::io::stdout;
use std::io::Write;
use std::process::Command;
use std::path::Path;
fn main(){
let mut input = String::new();
loop {
print!("> ");
stdout().flush().unwrap();
// input = String::from(""); // **<===== HERE**
stdin().read_line(&mut input).unwrap();
let mut parts = input.trim().split_whitespace();
let command = parts.next().unwrap();
let args = parts;
match command {
"cd" => {
let new_dir = args.peekable().peek().map_or("/", |x| *x);
let root = Path::new(new_dir);
if let Err(e) = std::env::set_current_dir(&root) {
eprintln!("{}", e);
}
},
"exit" => return,
command => {
let child = Command::new(command)
.args(args)
.spawn();
// gracefully handle malformed user input
match child {
Ok(mut child) => { child.wait().unwrap(); },
Err(e) => eprintln!("{}", e),
};
}
}
}
}
However, commenting out this line causes program to bug: here is the behavior before and after commenting:
usn#DESKTOP:~/bqy/rust/p2$ cargo run
warning: value assigned to `input` is never read
--> src/main.rs:8:10
|
8 | let mut input = String::new();
| ^^^^^
|
= note: `#[warn(unused_assignments)]` on by default
= help: maybe it is overwritten before being read?
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/p2`
> ls
Cargo.lock Cargo.toml src target
> ls
Cargo.lock Cargo.toml src target
> exit
usn#DESKTOP:~/bqy/rust/p2$ vi src/main.rs
usn#DESKTOP:~/bqy/rust/p2$ cargo run
Compiling p2 v0.1.0 (/mnt/d/MMM/projects/bqy/rust/p2)
Finished dev [unoptimized + debuginfo] target(s) in 1.13s
Running `target/debug/p2`
> ls
Cargo.lock Cargo.toml src target
> ls
ls: cannot access 'ls': No such file or directory
> exit
ls: cannot access 'ls': No such file or directory
ls: cannot access 'exit': No such file or directory
> ^C
usn#DESKTOP:~/bqy/rust/p2$ vi src/main.rs
Simple explanation of unwanted behavior is that command line is not resetted. So, why does the compiler complains ?
Thank you for reading.
Without the line to clear the buffer, its content is reused from one iteration to another. This is why your program doesn't work without
input = String::from("");
But this gives you a warning because you gave input a value when you declared it:
let mut input = String::new();
// ^^^^^^^^^^^^^^^^
and this value is not used, since literally the first use of input is to override it. So just don't give it a value:
let mut input;
In Rust this is perfectly safe and the compiler won't let you accidentally use input before you initialize it.
Alternatively, if you goal was to reuse the buffer to save on memory, you should use
input.clear();
because input = String::from(""); creates a completely new string with a new buffer and throws the other one away.

thread 'main' panicked at 'Box<Any>'

I am trying to learn Rust. I am following a book online which implements the unix program cat. Right now I trying to read the content of files passed as an argument like that cargo run file1.txt file2.txt but the program panics:
D:\rust\cat> cargo run .\src\test.txt
Compiling cat v0.1.0 (D:\rust\cat)
Finished dev [unoptimized + debuginfo] target(s) in 0.62s
Running `target\debug\cat.exe .\src\test.txt`
thread 'main' panicked at 'Box<Any>', src\main.rs:12:28
this is my program:
use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
match read_file(&args) {
Ok(content) => println!("{}", content),
Err(reason) => panic!(reason),
}
}
}
fn read_file(filenames: &Vec<String>) -> Result<String, io::Error> {
let mut content = String::new();
for filename in filenames {
let mut file = File::open(filename)?;
file.read_to_string(&mut content)?;
}
Ok(content)
}
Can anyone explain what I am missing here?
The first element of the Args iterator returned by std::env::args is tipically the path of executable (see the docs
for more details).
The error arises because you do not skip the first arg: the program binary is not a sequence of valid UTF-8 bytes.
The apparently non sense error thread 'main' panicked at 'Box<Any>' is because panic! is not used with the same arguments of
the format! syntax.
use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;
fn main() {
for filename in env::args().skip(1) {
match read_file(filename) {
Ok(content) => println!("{}", content),
Err(reason) => panic!("{}", reason),
}
}
}
fn read_file(filename: String) -> Result<String, io::Error> {
let mut content = String::new();
let mut file = File::open(filename)?;
file.read_to_string(&mut content)?;
Ok(content)
}

How to capture the output of a process piped into a Rust program?

I know how to read the command line arguments, but I am having difficulties reading the command output from a pipe.
Connect a program (A) that outputs data to my Rust program using a pipe:
A | R
The program should consume the data line by line as they come.
$ pwd | cargo run should print the pwd output.
OR
$ find . | cargo run should output the find command output which is more than 1 line.
Use BufRead::lines on a locked handle to standard input:
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let line = line.expect("Could not read line from standard in");
println!("{}", line);
}
}
If you wanted to reuse the allocation of the String, you could use the loop form:
use std::io::{self, Read};
fn main() {
let stdin = io::stdin();
let mut stdin = stdin.lock(); // locking is optional
let mut line = String::new();
// Could also `match` on the `Result` if you wanted to handle `Err`
while let Ok(n_bytes) = stdin.read_to_string(&mut line) {
if n_bytes == 0 { break }
println!("{}", line);
line.clear();
}
}
You just need to read from Stdin.
This is based on an example taken from the documentation:
use std::io;
fn main() {
loop {
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(len) => if len == 0 {
return;
} else {
println!("{}", input);
}
Err(error) => {
eprintln!("error: {}", error);
return;
}
}
}
}
It's mostly the docs example wrapped in a loop, breaking out of the loop when there is no more input, or if there is an error.
The other changes is that it's better in your context to write errors to stderr, which is why the error branch uses eprintln!, instead of println!. This macro probably wasn't available when that documentation was written.
use std::io;
fn main() {
loop {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("failed to read from pipe");
input = input.trim().to_string();
if input == "" {
break;
}
println!("Pipe output: {}", input);
}
}
OUTPUT:
[18:50:29 Abhinickz#wsl -> pipe$ pwd
/mnt/d/Abhinickz/dev_work/learn_rust/pipe
[18:50:46 Abhinickz#wsl -> pipe$ pwd | cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/pipe`
Pipe output: /mnt/d/Abhinickz/dev_work/learn_rust/pipe
You can do it in a pretty snazzy and concise way with rust's iterator methods
use std::io::{self, BufRead};
fn main() {
// get piped input
// eg `cat file | ./program`
// ( `cat file | cargo run` also works )
let input = io::stdin().lock().lines().fold("".to_string(), |acc, line| {
acc + &line.unwrap() + "\n"
});
dbg!(input);
}

stdin read_line does not wait for user input when called from a git hook

I'm an absolute Rust beginner trying to build a simple confirmation function (yes or no), but I can't get the user to type anything, the function just keeps looping without waiting for user input:
""
""
""
etc.
is the result of the simplified version below.
use std::process;
use std::io;
pub fn confirm() {
loop {
let mut answer = String::new();
io::stdin().read_line(&mut answer)
.ok()
.expect("Failed to read line");
println!("{:?}", answer);
}
}
I've built my function around the guessing game example, and the rest of my program does nothing much, just reading a file and printing text.
Perhaps is due to the way my program (a git hook) is launched?
Assuming that the problem is that your git commit hook is running in an non-interactive environment, you can follow the advice laid out in that question and directly open /dev/tty. Unlike STDIN, we don't treat it as a magical global variable and instead we pass it into the places we need:
use std::io::{self, BufRead, BufReader};
use std::fs::File;
type Tty = BufReader<File>;
fn open_tty() -> io::Result<Tty> {
let f = try!(File::open("/dev/tty"));
Ok(BufReader::new(f))
}
fn confirm(tty: &mut Tty) -> io::Result<String> {
let mut answer = String::new();
try!(tty.read_line(&mut answer));
Ok(answer)
}
fn inner_main() -> io::Result<()> {
let mut tty = try!(open_tty());
let answer = try!(confirm(&mut tty));
println!("The answer was: {}", answer);
Ok(())
}
fn main() {
inner_main().unwrap()
}
Note that this will not be platform independent. Specifically, this is very unlikely to work on Windows!
I've also gone ahead and allowed the io::Result to propagate throughout the program, only panicking at the outermost shell.
Are you testing the function on the Rust Playground? Running this program in a terminal seems to work fine. That being said, there is no guarantee that stdin will block, but you could change the function to check if the string is empty or not, and only return once it is isn't.
use std::io;
fn main() {
println!("{:?}", confirm());
}
fn confirm() -> String {
loop {
let mut answer = String::new();
io::stdin().read_line(&mut answer)
.ok()
.expect("Failed to read line");
if !answer.is_empty() && answer != "\n" && answer != "\r\n" {
return answer
}
}
}

Resources