Why doesn't my rust project output anything? - rust

my point of this program is to see if certain processes are running. My code checks with no problem, but it doesn't output anything.
It creates the file, but that's it. If anyone could help I would really appreciate it. I've already spent 3 weeks on the project I'm adding this code to, and it is really frustrating af.
Code:
use sysinfo::{ProcessExt, System, SystemExt};
use std::fs::{OpenOptions, write};
use std::io::Error;
fn get_process(pid: &str) -> Result<(), Error>
{
let _log = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("process_list.txt")?;
let p = System::new_all();
for mut _proc in p.processes_by_exact_name(pid)
{
let is_up = _proc.name();
if is_up == "Opera"
{
write("process_list.txt", b"Opera is up")?;
//}else if is_up == "Google Chrome"{
//println!("chrome is running");
//}else if is_up == "Firefox" {
//println!("firefox is running");
}else{
write("process_list.txt", b"NOTHING IS RUNNING!!!")?;
}
}
Ok(())
}
fn main()
{
loop {
get_process("Opera").ok();
}

You should check for errors instead of ignoring them. If you replace get_process("Opera").ok(); with get_process("Opera").unwrap();, you should see an error message complaining that your program couldn't open file process_list.txt for writing.
This is because you try to open the file twice: the first time in let _log = … then again when calling write("process_list.txt", …). But the second time fails because the file is still open from the first time.
You should either get rid of the _log variable since you don't use it, or use _log.write (…)?; to write to the file.

Related

Replacing unwrap() with ? made code more complicated instead of simplifying it

This code deletes PNG files in a folder and prints them. I wanted to simplify it a bit by replacing all the unwrap()'s with ?.
use std::fs;
use std::path::Path;
fn main() -> Result<(), std::io::Error> {
let path = Path::new("/home/alex/Desktop");
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().unwrap() == "png" {
fs::remove_file(&path)?;
println!("{}", path.file_name().unwrap().to_str().unwrap());
}
}
Ok(())
}
I found out I can't replace the unwrap()'s that are handling Option instead of Result. In this case, extension(), file_name(), and to_str().
I changed the code to solve that problem. However, the code just became more complicated:
use std::fs;
use std::path::Path;
fn main() -> Result<(), std::io::Error> {
let path = Path::new("/home/alex/Desktop");
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let ext = path.extension().ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Invalid file extension"))?;
if ext == "png" {
fs::remove_file(&path)?;
println!("{}", path.file_name().ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Invalid file name"))?.to_str().ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Invalid file name"))?);
}
}
}
Ok(())}
How to replace the unwrap()'s that are handling Option without making the code more complicated (especially, without so much nesting)? Or at least not as complicated as the one I shared?
In many of the cases where you were calling unwrap it wasn't actually an error, you just want to do something different (or not at all).
If you're only interested in the case where there is a png extension, check if that's what you got. It doesn't matter if it's None or .jpg.
Printing a filename can fail because filenames can have non unicode characters and Rust strings can't. In this case given it's presumably meant to be human readable, printing the to_string_lossy() output (replacing non-unicode characters) or printing it using debug mode (escaping non-unicode characters) is probably fine.
use std::fs;
use std::path::Path;
fn main() -> Result<(), std::io::Error> {
let path = Path::new("/home/alex/Desktop");
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if ext == "png" {
fs::remove_file(&path)?;
// I would keep expect here as extension would return None if there was no filename
// to_string_lossy returns a printable string - possibly replacing non-unicode characters
println!("{}", path.file_name().expect("File has no name").to_string_lossy());
}
}
}
}
Ok(())
}

Read lines in Rust with match result

I'm pretty new with Rust and trying to implement some basic stuff.
So one of the samples from the doc is about reading lines from a text file: https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html
The sample code works (obviously...) but I wanted to modify it to add some error handling but the compiler complains about the result I define
// the result after -> is not valid...
pub fn read_lines<P>(filename: P) -> std::result::Result<std::io::Lines<std::io::BufReader<std::fs::File>>, std::io::Error>
where P: AsRef<Path>, {
let file = File::open(filename);
let file = match file {
Ok(file) => io::BufReader::new(file).lines(),
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
// this is fine
pub fn read_lines2<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
I've tried different suggestions from the intellisense but no luck.
Any idea about how to define the result when there is an Ok/Err ?
If I understand the intent of your code correctly, here is a more canonical version:
use std::fs::File;
use std::io::{self, prelude::*};
use std::path::Path;
pub fn read_lines(filename: &Path) -> io::Lines<io::BufReader<File>> {
let file = File::open(filename).expect("Problem opening the file");
io::BufReader::new(file).lines()
}
If you want the caller to handle any errors, return something like io::Result<T> (an alias for std::result::Result<T, io::Error>), indicating a possible failure. However, if you decide to handle the error inside the function using something like panic!() or Result::expect(), then there is no need to return a Result to the caller since the function only returns to the caller if no error occurred.
Is this what you were looking for?
pub fn read_lines<P>(filename: P) -> std::result::Result<std::io::Lines<std::io::BufReader<std::fs::File>>, std::io::Error>
where P: AsRef<Path>, {
let file = File::open(filename);
match file {
Ok(file) => Ok(io::BufReader::new(file).lines()),
Err(error) => panic!("Problem opening the file: {:?}", error),
}
}
I removed the let file = and ; to enable implicit return, and added an Ok() wrapper around the happy path.

Reading ZIP file in Rust causes data owned by the current function

I'm new to Rust and am likely have a huge knowledge gap. Basically, I'm hoping to be create a utility function that would except a regular text file or a ZIP file and return a BufRead where the caller can start processing line by line. It is working well for non ZIP files but I am not understanding how to achieve the same for the ZIP files. The ZIP files will only contain a single file within the archive which is why I'm only processing the first file in the ZipArchive.
I'm running into the the following error.
error[E0515]: cannot return value referencing local variable `archive_contents`
--> src/file_reader.rs:30:9
|
27 | let archive_file: zip::read::ZipFile = archive_contents.by_index(0).unwrap();
| ---------------- `archive_contents` is borrowed here
...
30 | Ok(Box::new(BufReader::with_capacity(128 * 1024, archive_file)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
It seems the archive_contents is preventing the BufRead object from returning to the caller. I'm just not sure how to work around this.
file_reader.rs
use std::ffi::OsStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;
pub struct FileReader {
pub file_reader: Result<Box<BufRead>, &'static str>,
}
pub fn file_reader(filename: &str) -> Result<Box<BufRead>, &'static str> {
let path = Path::new(filename);
let file = match File::open(&path) {
Ok(file) => file,
Err(why) => panic!(
"ERROR: Could not open file, {}: {}",
path.display(),
why.to_string()
),
};
if path.extension() == Some(OsStr::new("zip")) {
// Processing ZIP file.
let mut archive_contents: zip::read::ZipArchive<std::fs::File> =
zip::ZipArchive::new(file).unwrap();
let archive_file: zip::read::ZipFile = archive_contents.by_index(0).unwrap();
// ERRORS: returns a value referencing data owned by the current function
Ok(Box::new(BufReader::with_capacity(128 * 1024, archive_file)))
} else {
// Processing non-ZIP file.
Ok(Box::new(BufReader::with_capacity(128 * 1024, file)))
}
}
main.rs
mod file_reader;
use std::io::BufRead;
fn main() {
let mut files: Vec<String> = Vec::new();
files.push("/tmp/text_file.txt".to_string());
files.push("/tmp/zip_file.zip".to_string());
for f in files {
let mut fr = match file_reader::file_reader(&f) {
Ok(fr) => fr,
Err(e) => panic!("Error reading file."),
};
fr.lines().for_each(|l| match l {
Ok(l) => {
println!("{}", l);
}
Err(e) => {
println!("ERROR: Failed to read line:\n {}", e);
}
});
}
}
Any help is greatly appreciated!
It seems the archive_contents is preventing the BufRead object from returning to the caller. I'm just not sure how to work around this.
You have to restructure the code somehow. The issue here is that, well, the archive data is part of the archive. So unlike file, archive_file is not an independent item, it is rather a pointer of sort into the archive itself. Which means the archive needs to live longer than archive_file for this code to be correct.
In a GC'd language this isn't an issue, archive_file has a reference to archive and will keep it alive however long it needs. Not so for Rust.
A simple way to fix this would be to just copy the data out of archive_file and into an owned buffer you can return to the parent. An other option might be to return a wrapper for (archive_contents, item_index), which would delegate the reading (might be somewhat tricky though). Yet another would be to not have file_reader.
Thanks to #Masklinn for the direction! Here's the working solution using their suggestion.
file_reader.rs
use std::ffi::OsStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Cursor;
use std::io::Error;
use std::io::Read;
use std::path::Path;
use zip::read::ZipArchive;
pub fn file_reader(filename: &str) -> Result<Box<dyn BufRead>, Error> {
let path = Path::new(filename);
let file = match File::open(&path) {
Ok(file) => file,
Err(why) => return Err(why),
};
if path.extension() == Some(OsStr::new("zip")) {
let mut archive_contents = ZipArchive::new(file)?;
let mut archive_file = archive_contents.by_index(0)?;
// Read the contents of the file into a vec.
let mut data = Vec::new();
archive_file.read_to_end(&mut data)?;
// Wrap vec in a std::io::Cursor.
let cursor = Cursor::new(data);
Ok(Box::new(cursor))
} else {
// Processing non-ZIP file.
Ok(Box::new(BufReader::with_capacity(128 * 1024, file)))
}
}
While the solution you have settled on does work, it has a few disadvantages. One is that when you read from a zip file, you have to read the contents of the file you want to process into memory before proceeding, which might be impractical for a large file. Another is that you have to heap allocate the BufReader in either case.
Another possibly more idiomatic solution is to restructure your code, such that the BufReader does not need to be returned from the function at all - rather, structure your code so that it has a function that opens the file, which in turn calls a function that processes the file:
use std::ffi::OsStr;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;
pub fn process_file(filename: &str) -> Result<usize, String> {
let path = Path::new(filename);
let file = match File::open(&path) {
Ok(file) => file,
Err(why) => return Err(format!(
"ERROR: Could not open file, {}: {}",
path.display(),
why.to_string()
)),
};
if path.extension() == Some(OsStr::new("zip")) {
// Handling a zip file
let mut archive_contents=zip::ZipArchive::new(file).unwrap();
let mut buf_reader = BufReader::with_capacity(128 * 1024,archive_contents.by_index(0).unwrap());
process_reader(&mut buf_reader)
} else {
// Handling a plain file.
process_reader(&mut BufReader::with_capacity(128 * 1024, file))
}
}
pub fn process_reader(reader: &mut dyn BufRead) -> Result<usize, String> {
// Example, just count the number of lines
return Ok(reader.lines().count());
}
fn main() {
let mut files: Vec<String> = Vec::new();
files.push("/tmp/text_file.txt".to_string());
files.push("/tmp/zip_file.zip".to_string());
for f in files {
match process_file(&f) {
Ok(count) => println!("File {} Count: {}", &f, count),
Err(e) => println!("Error reading file: {}", e),
};
}
}
This way, you don't need any Boxes and you don't need to read the file into memory before processing it.
A drawback to this solution would if you had multiple functions that need to be able to read from zip files. One way to handle that would be to define process_file to take a callback function to do the processing. First you would change the definition of process_file to be:
pub fn process_file<C>(filename: &str, process_reader: C) -> Result<usize, String>
where C: FnOnce(&mut dyn BufRead)->Result<usize, String>
The rest of the function body can be left unchanged. Now, process_reader can be passed into the function, like this:
process_file(&f, count_lines)
where count_lines would be the original simple function to count the lines, for instance.
This would also allow you to pass in a closure:
process_file(&f, |reader| Ok(reader.lines().count()))

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
}
}
}

How can I read a file line-by-line, eliminate duplicates, then write back to the same file?

I want to read a file, eliminate all duplicates and write the rest back into the file - like a duplicate cleaner.
Vec because a normal array has a fixed size but my .txt is flexible (am I doing this right?).
Read, lines in Vec + delete duplices:
Missing write back to file.
use std::io;
fn main() {
let path = Path::new("test.txt");
let mut file = io::BufferedReader::new(io::File::open(&path, R));
let mut lines: Vec<String> = file.lines().map(|x| x.unwrap()).collect();
// dedup() deletes all duplicates if sort() before
lines.sort();
lines.dedup();
for e in lines.iter() {
print!("{}", e.as_slice());
}
}
Read + write to file (untested but should work I guess).
Missing lines to Vec because it doesn't work without BufferedReader as it seems (or I'm doing something else wrong, also a good chance).
use std::io;
fn main() {
let path = Path::new("test.txt");
let mut file = match io::File::open_mode(&path, io::Open, io::ReadWrite) {
Ok(f) => f,
Err(e) => panic!("file error: {}", e),
};
let mut lines: Vec<String> = file.lines().map(|x| x.unwrap()).collect();
lines.sort();
// dedup() deletes all duplicates if sort() before
lines.dedup();
for e in lines.iter() {
file.write("{}", e);
}
}
So .... how do I get those 2 together? :)
Ultimately, you are going to run into a problem: you are trying to write to the same file you are reading from. In this case, it's safe because you are going to read the entire file, so you don't need it after that. However, if you did try to write to the file, you'd see that opening a file for reading doesn't allow writing! Here's the code to do that:
use std::{
fs::File,
io::{BufRead, BufReader, Write},
};
fn main() {
let mut file = File::open("test.txt").expect("file error");
let reader = BufReader::new(&mut file);
let mut lines: Vec<_> = reader
.lines()
.map(|l| l.expect("Couldn't read a line"))
.collect();
lines.sort();
lines.dedup();
for line in lines {
file.write_all(line.as_bytes())
.expect("Couldn't write to file");
}
}
Here's the output:
% cat test.txt
a
a
b
a
% cargo run
thread 'main' panicked at 'Couldn't write to file: Os { code: 9, kind: Other, message: "Bad file descriptor" }', src/main.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
You could open the file for both reading and writing:
use std::{
fs::OpenOptions,
io::{BufRead, BufReader, Write},
};
fn main() {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("test.txt")
.expect("file error");
// Remaining code unchanged
}
But then you'd see that (a) the output is appended and (b) all the newlines are lost on the new lines because BufRead doesn't include them.
We could reset the file pointer back to the beginning, but then you'd probably leave trailing stuff at the end (deduplicating is likely to have less bytes written than read). It's easier to just reopen the file for writing, which will truncate the file. Also, let's use a set data structure to do the deduplication for us!
use std::{
collections::BTreeSet,
fs::File,
io::{BufRead, BufReader, Write},
};
fn main() {
let file = File::open("test.txt").expect("file error");
let reader = BufReader::new(file);
let lines: BTreeSet<_> = reader
.lines()
.map(|l| l.expect("Couldn't read a line"))
.collect();
let mut file = File::create("test.txt").expect("file error");
for line in lines {
file.write_all(line.as_bytes())
.expect("Couldn't write to file");
file.write_all(b"\n").expect("Couldn't write to file");
}
}
And the output:
% cat test.txt
a
a
b
a
a
b
a
b
% cargo run
% cat test.txt
a
b
The less-efficient but shorter solution is to read the entire file as one string and use str::lines:
use std::{
collections::BTreeSet,
fs::{self, File},
io::Write,
};
fn main() {
let contents = fs::read_to_string("test.txt").expect("can't read");
let lines: BTreeSet<_> = contents.lines().collect();
let mut file = File::open("test.txt").expect("can't create");
for line in lines {
writeln!(file, "{}", line).expect("can't write");
}
}
See also:
What's the de-facto way of reading and writing files in Rust 1.x?
What is the best variant for appending a new line in a text file?

Resources