[Rust Serial] Bài 5: Thao tác với Texting processing, File and Directory trong Rust
Bài này chúng ta sẽ tìm hiểu các thao tác với File, Directory và xử Text trong Rust. Đây là các kỹ năng cần thiết
Outline
- Chương 0: Thao tác với File
- Chương 1: Thao tác với thư mục
- Chương 2: Text Processing
- Chương 3: Một số thư viện
References:
Chương 0: Thao tác với File
Các kiểu thao tác vói file
- Mở một file
- Tạo mới một file
- Đóng một file
- Ghi tới file
- Đọc metadata từ file
- Đọc dữ liệu từ file
Eg 1:
Eg 2: Tạo một file mới và ghi
Chú ý hàm write
cần mảng các bytes &[u8]
Mở một file
let f = File::open("hello.txt").expect("Could not open file");
Cách khác sử dụng OpenOptions
fn open_with_open_options() {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("foo.txt")
.unwrap();
}
- Mở với quyền đọc, ghi, ghi thêm
pub fn read(&mut self, read: bool) -> &mut Self
pub fn write(&mut self, write: bool) -> &mut Self
pub fn append(&mut self, append: bool) -> &mut Self
pub fn truncate(&mut self, truncate: bool) -> &mut Self
pub fn create(&mut self, create: bool) -> &mut Self
pub fn create_new(&mut self, create_new: bool) -> &mut Self
Khác nhau của create
và create_new
là create_new
sẽ lỗi nếu file đã tồn tại
Tạo một file mới
fn create_file() {
let mut f = File::create("hello2.txt").expect("Could not create file");
}
Đóng một file
fn write_file() {
let mut f = File::create("hello2.txt").expect("Could not create file");
f.write("buffer".as_bytes())
.expect("Could not write to file");
f.close();
}
Ghi tới file
fn write_file() {
let mut f = File::create("hello2.txt").expect("Could not create file");
f.write("buffer".as_bytes())
.expect("Could not write to file");
}
Đọc metadata của file
- File type
- is dir or not?
- symbol link ?
- len
- permission
- modified time
- access time
- created time
fn main() -> std::io::Result<()> {
use std::fs;
let metadata = fs::metadata("foo.txt")?;
println!("{:?}", metadata.file_type());
assert!(!metadata.is_dir());
Ok(())
}
refer: https://doc.rust-lang.org/std/fs/struct.Metadata.html
Đọc dữ liệu từ file
- Đọc theo từng single byte
- Đọc vào buffer
- Đọc theo từng chunk bytes
- Đọc theo từng dòng
- Đọc đến cuối
- Đọc từ cuối (Di chuyển con trỏ để đọc)
Đọc theo từng single byte
fn read_with_single_bytes() {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("hello.txt")
.unwrap();
for i in file.bytes() {
println!("Byte: {:?}", i.unwrap());
}
}
Đọc vào buffer
fn read_with_single_bytes() {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("hello.txt")
.unwrap();
let mut buffer = [0; 10];
file.read(&mut buffer).unwrap();
println!("Buffer contents: {:?}", buffer);
}
Đọc theo từng chunk bytes
fn read_with_buffer() {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("hello.txt")
.unwrap();
let mut buffer = [0; 10];
file.read(&mut buffer).unwrap();
println!("Buffer contents: {:?}", buffer);
}
Đọc đến cuối
Nó nhận đầu vào là 1 vector
fn read_to_end() {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("hello.txt")
.unwrap();
let mut buffer = vec![0; 1000];
file.read_to_end(&mut buffer).unwrap();
println!("Buffer contents: {:?}", buffer);
}
Đọc từ cuối (Di chuyển con trỏ để đọc)
fn read_with_reader4_from_end() {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("hello.txt")
.unwrap();
const CAP: usize = 1024 * 128; // 128 KB
let mut reader = BufReader::with_capacity(CAP, file);
// let mut reader = BufReader::new(file);
let r = reader.seek(std::io::SeekFrom::End(0));
let mut buf = String::new();
reader.seek_relative(-10);
reader.read_to_string(&mut buf);
println!("Line: {:?}", buf);
}
Đọc sử dụng BufReader
fill_buf
: Trở về nội dung của buffer bên trong, fill it với nhiều dữ liệu nhấtconsume
: Nói rằng buffer đã tiêu thụ n bytes và không được trở về từ hàmread
nữa.read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize>
: Đọc đến ký tựbyte
thì dừng lạiskip_until
: Bỏ qua đến khi gặp ký tựread_lin
đọc đến khi gặp0xA
thì dừngrewind
: Quay lại đầu file, streamseek
: Di chuyển con trỏ đọc đến vị trí
pub enum SeekFrom {
Start(u64),
End(i64),
Current(i64),
}
Homework 1.1: Read the n th last line of a file
Homework 1.2: Figure out about std::path::Path
Chương 1: Thao tác với thư mục
https://rust-lang-nursery.github.io/rust-cookbook/file/dir.html
List các danh sách files từ một folder
- Cách 1:
Hàm
fs::read_dir
fn dir_read_paths() {
let paths = fs::read_dir("./").unwrap();
for path in paths {
println!("Name: {}", path.unwrap().path().display())
}
}
- Cách 2:
use glob::glob;
fn main() {
for e in glob("../*").expect("Failed to read glob pattern") {
println!("{}", e.unwrap().display());
}
}
- Cách 3:
use walkdir::WalkDir;
fn main() {
for e in WalkDir::new(".").into_iter().filter_map(|e| e.ok()) {
if e.metadata().unwrap().is_file() {
println!("{}", e.path().display());
}
}
}
List recusely files
fn recurse(path: impl AsRef<Path>) -> Vec<PathBuf> {
let Ok(entries) = read_dir(path) else { return vec![] };
entries.flatten().flat_map(|entry| {
let Ok(meta) = entry.metadata() else { return vec![] };
if meta.is_dir() { return recurse(entry.path()); }
if meta.is_file() { return vec![entry.path()]; }
vec![]
}).collect()
}
Homework 2: Read the n th last line of a file
Chương 2: Text Processing
Refer: https://docs.rs/regex/latest/regex/
Regex
[dependencies]
regex = "1.3.9"
Nhắc lại một số kiến thức về regex
- Bắt đầu một chuỗi:
^
- Kết thúc chuỗi:
$
- Ký tự digit:
\d
- Một trong các ký tự:
[aebc]
- Bất kỳ ký tự:
.
- Các ký tự chữ: [a-zA-Z]
- Repetition:
ha*
: h theo sau bởi 0 hoặc nhiều aha+
: h theo sau bởi 1 hoặc nhiều aha?
: h theo sau bởi 0 hoặc 1 aha{3}
: h theo sau bởi 3 lần 3
- Caputures:
- Sử dụng
()
. Eg:let re = Regex::new(r"Hello (?<name>\w+)!").unwrap();
. Truy xấut - Đặt tên cho captures:
(?<name>....)
.Eg:let re = Regex::new(r"Hello (?<name>\w+)!").unwrap();
- Sử dụng
use regex::Regex;
pub fn regex1() {
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
assert!(re.is_match("2024-07-22"));
// Anchors
let re = Regex::new(r"^hello").unwrap(); // Must start with hello
let re = Regex::new(r"hello$").unwrap(); // Must end with hello
// Dot
let re = Regex::new(r"h.llo").unwrap(); // Matches h+any char+llo
// Character class
let re = Regex::new(r"h[ae]llo").unwrap(); // Matches hallo or hello
// Repetition
let re = Regex::new(r"ha*").unwrap(); // h followed by 0 or more a
let re = Regex::new(r"ha+").unwrap(); // h followed by 1 or more a
let re = Regex::new(r"ha?").unwrap(); // h followed by 0 or 1 a
// Grouping
let re = Regex::new(r"(ha){3}").unwrap(); // ha ha ha
let re = Regex::new(r"(ha){2,4}").unwrap(); // ha ha ha {2 or 4 times}
}
- Validate email
pub fn validate_email() {
let re = Regex::new(r"^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+\.[a-z]{2,4}$").unwrap();
assert!(re.is_match("hello@example.com"));
assert!(re.is_match("tuanpa_abc_xyz@example.org"));
assert!(re.is_match("tuanpa_abc_xyz.kitto@example.vn"));
}
- Validate phone number
pub fn validate_phone_number() {
let re = Regex::new(r"^\+?\d{1,3}?\d{9,10}$").unwrap();
assert!(re.is_match("+84901234567"));
assert!(re.is_match("0901234567"));
assert!(re.is_match("84901234567"));
assert!(re.is_match("841234567"));
}
- Validate URL
pub fn validate_url() {
let re = Regex::new(r"^https?://[a-z0-9-.]{2,}\.[a-z]{2,4}(:[0-9]{2,5})?/?.*$").unwrap();
assert!(re.is_match("https://example.com"));
assert!(!re.is_match("example.com")); // Missing https
}
- Validate hashtag
pub fn validate_hashtag() {
let re = Regex::new(r"^#[a-zA-Z0-9_]+$").unwrap();
assert!(re.is_match("#hello"));
assert!(re.is_match("#hello_world"));
assert!(!re.is_match("#hello world")); // No space allowed
assert!(!re.is_match("#hello-world")); // No dash allowed
}
- Tìm kiếm số trong string:
find_iter
pub fn find_all_numbers_in_string() {
let re = Regex::new(r"\d+").unwrap();
let text = "Rust 2022";
let numbers: Vec<&str> = re.find_iter(text).map(|m| m.as_str()).collect();
assert_eq!(numbers, vec!["2022"]);
}
- Thay thế các số:
replace_all
pub fn replace_occurrences() {
let re = Regex::new(r"\d+").unwrap();
let text = "Rust 2022";
let new_text = re.replace_all(text, "2023");
assert_eq!(new_text, "Rust 2023");
}
- Chia tách:
split
pub fn split_strings() {
let re = Regex::new(r"\s+").unwrap();
let splits = re.split("Hello world! How");
assert_eq!(
splits.collect::<Vec<&str>>(),
&["Hello", "world!", "", "How"]
);
}
- UTF8
pub fn regex_on_utf8_strings2() {
let unicode_re = Regex::new(r"ça").unwrap();
assert!(unicode_re.is_match("ça va?"));
let byte_re = Regex::new(r"(?-u)ça").unwrap();
assert!(!byte_re.is_match("ça va?")); // Fails on byte string
}
Một sộ giống nhau
[0-9]
(?-u:\d)
[[:digit:]]
[\d&&\p{ascii}]
Json file
-
from_str
-
to_string()
-
Thư viện
serde
,bosh
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct User {
fingerprint: String,
location: String,
}
fn main() {
// The type of `j` is `&str`
let j = "
{
\"fingerprint\": \"0xF9BA143B95FF6D82\",
\"location\": \"Menlo Park, CA\"
}";
let u: User = serde_json::from_str(j).unwrap();
println!("{:#?}", u);
let address = Address {
street: "10 Downing Street".to_owned(),
city: "London".to_owned(),
};
// Serialize it to a JSON string.
let j = serde_json::to_string(&address)?;
}
Nom library
- Phong cách để xử lý text bởi duyệt qua từ byte một
Problem 1:
Given a input #6AFA23
. Parse this input and produce a struct
pub Color {
red: u8, // 0x6A
blue: u8, // 0xFA
green: u8 // 0x23
}
Solution:
use nom::{
bytes::complete::{tag, take_while_m_n},
combinator::map_res,
sequence::tuple,
IResult,
};
#[derive(Debug, PartialEq)]
pub struct Color {
red: u8, // 0x6A
blue: u8, // 0xFA
green: u8, // 0x23
}
fn from_hex(input: &str) -> Result<u8, std::num::ParseIntError> {
let a = u8::from_str_radix(input, 16);
a
}
fn is_hex_digit(c: char) -> bool {
c.is_digit(16)
}
fn hex_primary(input: &str) -> IResult<&str, u8> {
map_res(take_while_m_n(2, 2, is_hex_digit), from_hex)(input)
}
fn hex_color(input: &str) -> IResult<&str, Color> {
let (input, _) = tag("#")(input)?;
let (input, (red, green, blue)) = tuple((hex_primary, hex_primary, hex_primary))(input)?;
Ok((input, Color { red, green, blue }))
}
#[cfg(test)]
mod tests {
type Error = Box<dyn std::error::Error>;
type Result<T> = core::result::Result<T, Error>; // For tests.
use super::*;
fn test_hex_color() {
assert_eq!(
hex_color("#2F14DF"),
Ok((
"",
Color {
red: 47,
green: 20,
blue: 223,
}
))
);
}
}
What is nom?
- nom is a parser library
- nom is a paser combinator library
- nom is not bassed on grammars or generators.
The Parser
Trait
trait Parser<I, O, E> {
fn parse(&mut self, input: I) -> IResult<I, O, E>;
...
}
- Thực hiện
F: FnMut(I) -> IResult<I, O, E>
The IResult
Type
- Encodes the result of a parse
- type
IResult<I, O, E> = Result<(I, O), Err(E)>
Possible Parse Outcomes
- Success:
OK(I,O)
- Error:
Err(nom::Err:Error(E))
- Failure:
Err(nom::Err::Failure(E))
- Incomplete:
Err(nom::Err::Incomplete(Needed))
Homework 3: HTTP parser using nom library
nom
, nom_superme
...