I'm totally stuck reading a file from a variable path structure of a zip file without decompressing it.
My file is located here:
/info/[random-string]/info.json
Where [random-string] is the only file in the info folder.
So its like read the 'info' folder read the first folder read the 'info.json'.
Any ideas how to do that with one of these libraries (zip or rc_zip)?
let file_path = file.to_str().unwrap();
let file = File::open(file_path).unwrap();
let reader = BufReader::new(file);
let mut archive = zip::ZipArchive::new(reader).unwrap();
let info_folder = archive.by_name("info").unwrap();
// how to list files of info_folder
Here you are:
use std::error::Error;
use std::ffi::OsStr;
use std::fs::File;
use std::path::Path;
use zip::ZipArchive; // zip 0.5.13
fn main() -> Result<(), Box<dyn Error>> {
let archive = File::open("./info.zip")?;
let mut archive = ZipArchive::new(archive)?;
// iterate over all files, because you don't know the exact name
for idx in 0..archive.len() {
let entry = archive.by_index(idx)?;
let name = entry.enclosed_name();
if let Some(name) = name {
// process only entries which are named info.json
if name.file_name() == Some(OsStr::new("info.json")) {
// the ancestors() iterator lets you walk up the path segments
let mut ancestors = name.ancestors();
// skip self - the first entry is always the full path
ancestors.next();
// skip the random string
ancestors.next();
let expect_info = ancestors.next();
// the reminder must be only 'info/' otherwise this is the wrong entry
if expect_info == Some(Path::new("info/")) {
// do something with the file
println!("Found!!!");
break;
}
}
}
}
Ok(())
}
The following code opens a file:
use std::fs;
use std::io;
fn main() {
println!("Give me the absolute path to the file you want to read.");
let mut to_read = String::new();
io::stdin().read_line(&mut to_read).expect("Failed to read line");
to_read = to_read.trim_end().to_string();
let contents = fs::read_to_string(to_read).expect("Something went wrong reading the file");
println!("{}", contents.trim() );
}
From what I've read about .to_string() it converts the given value to a String.
What confuses me is that in my code the given value, i.e. the value assigned to the variable to_read is already a String
at the moment of its assignment: let mut to_read = String::new();. I discovered through
tinkering, that the line to_read = to_read.trim_end().to_string(); is necessary for my code to work, otherwise Rust panics with the message:
'Something went wrong reading the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
Why is it so?
trim_end returns a &str: a slice, that is a reference over a portion of the initial string.
So if you do
to_read = to_read.trim_end()
then you're trying to assign a &str to a variable of type String.
The solution you took was to build a new String from the &str with to_string(). While this works, it's uselessly expensive as your don't need a String later in read_to_string.
A better solution would be to keep the &str, in a new variable, which can have the same name:
use std::fs;
use std::io;
fn main() {
println!("Give me the absolute path to the file you want to read.");
let mut to_read = String::new();
io::stdin().read_line(&mut to_read).expect("Failed to read line");
let to_read = to_read.trim_end(); // same name but new variable
let contents = fs::read_to_string(to_read).expect("Something went wrong reading the file");
println!("{}", contents.trim() );
}
i have made this code to check for alive urls in a text file it was first to check for a single url the script worked but then i wanted to make it multithreaded i got this error
error
here is the original code :
use hyper_tls::HttpsConnector;
use hyper::Client;
use tokio::io::BufReader;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let https = HttpsConnector::new();
let url = std::env::args().nth(1).expect("no list given");
let client = Client::builder().build::<_, hyper::Body>(https);
let reader = BufReader::new(url);
let lines = reader.lines();
for l in lines {
let sep = l.parse()?;
// Await the response...
let resp = client.get(sep).await?;
if resp.status() == 200 {
println!("{}", l);}
if resp.status() == 301 {
println!("{}", l); }
}
Ok(())
}
the issue seems to be that you are passing in the file's name as opposed to its content to the BufReader.
In order to read the contents instead, you can use a tokio::fs:File.
Here's an example of reading a file and printing its lines to stdout using tokio and a BufReader:
use tokio::{
fs::File,
io::{
// This trait needs to be imported, as the lines function being
// used on reader is defined there
AsyncBufReadExt,
BufReader
}
};
#[tokio::main]
async fn main() {
// get file command line argument
let file_argument = std::env::args().nth(1).expect("Please provide a file as command line argument.");
// open file
let file = File::open(file_argument).await.expect("Failed to open file");
// create reader using file
let reader = BufReader::new(file);
// get iterator over lines
let mut lines = reader.lines();
// this has to be used instead of a for loop, since lines isn't a
// normal iterator, but a Lines struct, the next element of which
// can be obtained using the next_line function.
while let Some(line) = lines.next_line().await.expect("Failed to read file") {
// print current line
println!("{}", line);
}
}
I just started using rust, and i'm having trouble finding out if a path is a file or directory. I have this function that gets input, which i'm using to get the path:
pub fn input(msg: &str, v: &mut String) {
println!("{}", msg);
stdin().read_line(v).unwrap();
}
When i take input like this:
let mut path = String::new();
input("What is the path to your file/directory?", &mut path);
The I try to make a Path with it, and check if it's a file:
let file = Path::new(&path);
println!("{}", file.is_file());
Which prints false
When I make the Path from a set String, it works:
let file = Path::new("../directory/file.js");
println!("{}", file.is_file());
Prints true
I'm sure that i'm giving it a valid file path in the input function. I've read the docs to try to find what i'm doing wrong but I can't
Any idea whats wrong here?
Is there a good way to handle the ownership of a file held within a struct using Rust? As a stripped down example, consider:
// Buffered file IO
use std::io::{BufReader,BufRead};
use std::fs::File;
// Structure that contains a file
#[derive(Debug)]
struct Foo {
file : BufReader <File>,
data : Vec <f64>,
}
// Reads the file and strips the header
fn init_foo(fname : &str) -> Foo {
// Open the file
let mut file = BufReader::new(File::open(fname).unwrap());
// Dump the header
let mut header = String::new();
let _ = file.read_line(&mut header);
// Return our foo
Foo { file : file, data : Vec::new() }
}
// Read the remaining foo data and process it
fn read_foo(mut foo : Foo) -> Foo {
// Strip one more line
let mut header_alt = String::new();
let _ = foo.file.read_line(&mut header_alt);
// Read in the rest of the file line by line
let mut data = Vec::new();
for (lineno,line) in foo.file.lines().enumerate() {
// Strip the error
let line = line.unwrap();
// Print some diagnostic information
println!("Line {}: val {}",lineno,line);
// Save the element
data.push(line.parse::<f64>().unwrap());
}
// Export foo
Foo { data : data, ..foo}
}
fn main() {
// Initialize our foo
let foo = init_foo("foo.txt");
// Read in our data
let foo = read_foo(foo);
// Print out some debugging info
println!("{:?}",foo);
}
This currently gives the compilation error:
error[E0382]: use of moved value: `foo.file`
--> src/main.rs:48:5
|
35 | for (lineno,line) in foo.file.lines().enumerate() {
| -------- value moved here
...
48 | Foo { data : data, ..foo}
| ^^^^^^^^^^^^^^^^^^^^^^^^^ value used here after move
|
= note: move occurs because `foo.file` has type `std::io::BufReader<std::fs::File>`, which does not implement the `Copy` trait
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
error: Could not compile `rust_file_struct`.
To learn more, run the command again with --verbose.
And, to be sure, this makes sense. Here, lines() takes ownership of the buffered file, so we can't use the value in the return. What's confusing me is a better way to handle this situation. Certainly, after the for loop, the file is consumed, so it really can't be used. To better denote this, we could represent file as Option <BufReader <File>>. However, this causes some grief because the second read_line call, inside of read_foo, needs a mutable reference to file and I'm not sure how to obtain one it it's wrapped inside of an Option. Is there a good way of handling the ownership?
To be clear, this is a stripped down example. In the actual use case, there are several files as well as other data. I've things structured in this way because it represents a configuration that comes from the command line options. Some of the options are files, some are flags. In either case, I'd like to do some processing, but not all, of the files early in order to throw the appropriate errors.
I think you're on track with using the Option within the Foo struct. Assuming the struct becomes:
struct Foo {
file : Option<BufReader <File>>,
data : Vec <f64>,
}
The following code is a possible solution:
// Reads the file and strips the header
fn init_foo(fname : &str) -> Foo {
// Open the file
let mut file = BufReader::new(File::open(fname).unwrap());
// Dump the header
let mut header = String::new();
let _ = file.read_line(&mut header);
// Return our foo
Foo { file : Some(file), data : Vec::new() }
}
// Read the remaining foo data and process it
fn read_foo(foo : Foo) -> Option<Foo> {
let mut file = foo.file?;
// Strip one more line
let mut header_alt = String::new();
let _ = file.read_line(&mut header_alt);
// Read in the rest of the file line by line
let mut data = Vec::new();
for (lineno,line) in file.lines().enumerate() {
// Strip the error
let line = line.unwrap();
// Print some diagnostic information
println!("Line {}: val {}",lineno,line);
// Save the element
data.push(line.parse::<f64>().unwrap());
}
// Export foo
Some(Foo { data : data, file: None})
}
Note in this case that read_foo returns an optional Foo due to the fact that the file could be None.
On a side note, IMO, unless you absolutely need the BufReader to be travelling along with the Foo, I would discard it. As you've already found, calling lines causes a move, which makes it difficult to retain within another struct. As a suggestion, you could make the file field simply a String so that you could always derive the BufReader and read the file when needed.
For example, here's a solution where a file name (i.e. a &str) can be turned into a Foo with all the line processing done just before the construction of the struct.
// Buffered file IO
use std::io::{BufReader,BufRead};
use std::fs::File;
// Structure that contains a file
#[derive(Debug)]
struct Foo {
file : String,
data : Vec <f64>,
}
trait IntoFoo {
fn into_foo(self) -> Foo;
}
impl IntoFoo for &str {
fn into_foo(self) -> Foo {
// Open the file
let mut file = BufReader::new(File::open(self).unwrap());
// Dump the header
let mut header = String::new();
let _ = file.read_line(&mut header);
// Strip one more line
let mut header_alt = String::new();
let _ = file.read_line(&mut header_alt);
// Read in the rest of the file line by line
let mut data = Vec::new();
for (lineno,line) in file.lines().enumerate() {
// Strip the error
let line = line.unwrap();
// Print some diagnostic information
println!("Line {}: val {}",lineno,line);
// Save the element
data.push(line.parse::<f64>().unwrap());
}
Foo { file: self.to_string(), data }
}
}
fn main() {
// Read in our data from the file
let foo = "foo.txt".into_foo();
// Print out some debugging info
println!("{:?}",foo);
}
In this case, there's no need to worry about the ownership of the BufReader because it's created, used, and discarded in the same function. Of course, I don't fully know your use case, so this may not be suitable for your implementation.