欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Rust文本處理快速入門

 更新時間:2024年03月31日 11:35:41   作者:又耳筆記  
編程過程中有許多類型的數(shù)據(jù)要處理,其中文本處理必不可少,本文主要介紹了Rust文本處理快速入門 ,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

編程過程中有許多類型的數(shù)據(jù)要處理,其中文本處理必不可少,本文主要是記錄在使用Rust開發(fā)的過程中處理文本相關(guān)數(shù)據(jù)的一些代碼,而文本可以分為結(jié)構(gòu)化和非結(jié)構(gòu)化的文本,比如JSON和小說文本(沒有固定格式的文本)。

這里以兩種格式文本為例

  • Nginx的訪問日志
  • Caddy的訪問日志

為了不使文章過于冗長,大家可以根據(jù)自己需要將下面的數(shù)據(jù)復(fù)制成多行,然后自行測試, 或者問ChatGPT之類的AI給你生成一些樣本數(shù)據(jù), 比如問AI問題:"給我十條NGINX的訪問日志樣本數(shù)據(jù)"。

nginx的訪問日志測試樣本如下:

172.17.0.1 - - [20/Dec/2023:01:37:27 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
172.17.0.1 - - [20/Dec/2023:01:37:27 +0000] "GET /hello HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
172.17.0.1 - - [20/Dec/2023:01:37:27 +0000] "GET /hello HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"

上面的日志對應(yīng)的日志格式如下:

'$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

caddy的訪問日志測試樣本如下:

{"level":"info","ts":1683783840.9822006,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"::1","remote_port":"56352","proto":"HTTP/1.1","method":"GET","host":"localhost:20023","uri":"/","headers":{"Accept":["*/*"],"User-Agent":["curl/7.29.0"]}},"user_id":"","duration":0.000221154,"size":17060,"status":200,"resp_headers":{"Server":["Caddy"],"Etag":["\"rudac9d5w\""],"Content-Type":["text/html; charset=utf-8"],"Last-Modified":["Tue, 09 May 2023 01:19:21 GMT"],"Accept-Ranges":["bytes"],"Content-Length":["17060"]}}
{"level":"info","ts":1683783841.9822006,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"::1","remote_port":"56352","proto":"HTTP/1.1","method":"GET","host":"localhost:20023","uri":"/hello","headers":{"Accept":["*/*"],"User-Agent":["curl/7.29.0"]}},"user_id":"","duration":0.000221154,"size":17060,"status":200,"resp_headers":{"Server":["Caddy"],"Etag":["\"rudac9d5w\""],"Content-Type":["text/html; charset=utf-8"],"Last-Modified":["Tue, 09 May 2023 01:19:21 GMT"],"Accept-Ranges":["bytes"],"Content-Length":["17060"]}}
{"level":"info","ts":1683783841.9822006,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"::1","remote_port":"56352","proto":"HTTP/1.1","method":"GET","host":"localhost:20023","uri":"/hello","headers":{"Accept":["*/*"],"User-Agent":["curl/7.29.0"]}},"user_id":"","duration":0.000221154,"size":17060,"status":200,"resp_headers":{"Server":["Caddy"],"Etag":["\"rudac9d5w\""],"Content-Type":["text/html; charset=utf-8"],"Last-Modified":["Tue, 09 May 2023 01:19:21 GMT"],"Accept-Ranges":["bytes"],"Content-Length":["17060"]}}

Caddy的訪問日志是JSON格式,就不需要什么額外的說明了。

本文代碼的所有Rust依賴如下:

因?yàn)镽ust的標(biāo)準(zhǔn)庫非常精簡(簡陋), 所以很多操作都需要借助第三方庫,比如這里處理JSON的庫serde.

[dependencies]
encoding_rs = "0.8.33"
regex = "1.10.2"
serde_json = "1.0.108"

快速入門

假設(shè)我們的任務(wù)是統(tǒng)計日志中每個URL的訪問次數(shù)。

Caddy日志解析

Caddy的日志格式是每行都是一個合法的JSON格式的文本,所以直接使用serde_json處理即可。

// https://youerning.top/post/rust-text-processing-tutorial/
use std::collections::HashMap;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Result;
use std::fs::File;
use serde_json::Value;


fn main() -> Result<()>{
    let filepath = "caddy.log";
    let file = File::open(filepath)?;
    let reader = BufReader::new(file);

    let mut url_counter = HashMap::new();
    for line in reader.lines() {
        match line  {
            Ok(line) => {
                // println!("line: {line}");
                if let Err(_) = serde_json::from_str::<Value>(&line) {
                    continue
                }
                
                let data: Value = serde_json::from_str(&line).unwrap();
                if let None = data.get("request") {
                    continue
                }
                // 這樣的代碼太形式化了,應(yīng)該有類似于GJSON之類的庫, 不夠我沒有用過
                // 所以這里就這樣吧, 后文用展開宏節(jié)省一下代碼。
                // 其實(shí)這里也可以用Options的and_then方法,但是還需要寫一個匿名函數(shù),不是很喜歡。
                if let None = data.get("request").unwrap().get("uri") {
                    continue
                }
                let uri = data.get("request").unwrap().get("uri").unwrap();
                if let None = uri.as_str() {
                    continue
                }
                let uri = uri.as_str().unwrap();
                // *url_counter.entry(uri.to_owned()).or_insert(0) += 1;
                let v = url_counter.entry(uri.to_owned()).or_insert(0);
                *v += 1;
            },
            Err(err) => {
                return Err(err)
            }
        }
    }
    println!("url_counter: {url_counter:?}");
    
    Ok(())
}

Nginx日志解析

類似于Nginx這樣的純文本格式,必須得預(yù)先知道文本的格式,這可以通過肉眼觀察或者查看輸出端的配置來了解格式,不然的話沒辦法精確的處理,至少是不能將每個字段的值剝離出來。

根據(jù)觀察或者說查看Nginx的配置文件,我們知道我們要取的數(shù)據(jù)在第一個用雙引號""包裹起來的字符串內(nèi), 比如"GET / HTTP/1.1"。

解析文本有很多辦法,大致分為兩種,使用正則表達(dá)式或者不使用正則表達(dá)式,這里選擇的方法是不使用正則表達(dá)式,因?yàn)檎齽t表達(dá)式的維護(hù)難度有點(diǎn)大。

// https://youerning.top/post/rust-text-processing-tutorial/
use std::collections::HashMap;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Result;
use std::fs::File;


fn main() -> Result<()>{
    let filepath = "nginx.log";
    let file = File::open(filepath)?;
    let reader = BufReader::new(file);

    let mut url_counter = HashMap::new();
    for line in reader.lines() {
        match line  {
            Ok(line) => {
                // println!("line: {line}");
                let spilts:Vec<&str> = line.split_whitespace().collect();
                if spilts.len() < 13 {
                    continue
                }
                // 注意: 這里不會考慮包含代理的日志記錄
                // 如果是代理的日志記錄可能是 http://xxxx:xxx/abc這種格式
                if !spilts.get(6).unwrap().starts_with("/") {
                    continue
                }

                let uri = *spilts.get(6).unwrap();
                // *url_counter.entry(uri.to_owned()).or_insert(0) += 1;
                let v = url_counter.entry(uri.to_owned()).or_insert(0);
                *v += 1;
            },
            Err(err) => {
                return Err(err)
            }
        }
    }
    println!("url_counter: {url_counter:?}");
    
    Ok(())
}

兩個的代碼結(jié)果應(yīng)該都是如下:

url_counter: {"/": 1, "/hello": 2}

文件讀取

一般來說文本都是以文件的形式存在的,這里討論的也主要是以文件形式存在的文本,至于網(wǎng)絡(luò)數(shù)據(jù)的文本需要根據(jù)對應(yīng)的協(xié)議來處理了。

獲取文件句柄(打開文件)

在讀取文本之前自然是需要先打開文件或者說獲得文件句柄的。
如果只關(guān)心打不打得開,那么可以直接通過問號?操作符將錯誤直接往外拋。

use std::io::Result;
use std::fs::File;


fn main() -> Result<()>{
    let filepath = "caddy.log";
    let file = File::open(filepath)?;
    Ok(())
}

如果我們關(guān)心錯誤,那么可以用模式匹配判斷一下, **io::Error有很多類型的, 這里僅判斷了不存在的類型 **

use std::io::{Result, ErrorKind};
use std::fs::File;


fn main() -> Result<()>{
    let filepath = "caddy.log";
    let file = match File::open(filepath) {
        Ok(file) => file,
        Err(err) => {
            if err.kind() == ErrorKind::NotFound{
                println!("文件不存在");
            }
            return Err(err)
        }
    };
    Ok(())
}

如果只是判斷文件不存在還有一些簡單的方法,比如:

use std::path::Path;


fn main() {
    let path = Path::new("caddy.logx");
    if !path.exists() {
        println!("文件不存在");
    }
}

編碼

當(dāng)獲取了文件句柄就可以讀取文件內(nèi)容了,但是我們總要時刻注意文件的編碼是什么,默認(rèn)情況下Rust提供的一些方法都是以UTF8格式來讀取文件的,比如

use std::io::{Result, Read};
use std::fs::File;


fn main() -> Result<()>{
    let filepath = "caddy.log";
    let mut file = File::open(filepath)?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    println!("content: {content}");
    Ok(())
}

雖然UTF8是主流,但是,但是,但是。。。還有一些例外,比如GBK。

如果我們使用上面的代碼讀取GBK格式的文件,那么會有以下報錯。

Error: Error { kind: InvalidData, message: "stream did not contain valid UTF-8" }

所以,我們需要指定編碼,這需要使用第三方庫encoding_rs, 可以通過cargo add encoding_rs添加依賴,本文使用的是0.8.33

值得注意的是: 非GBK的數(shù)據(jù)不一定會失敗, 比如全是ASCII字符的文本。

use std::io::{Result, Error, ErrorKind};
use std::fs;
use encoding_rs::GBK;


fn main() -> Result<()>{
    let filepath = "gbk.log";
    let content = fs::read(&filepath)?;
    println!("{}", content.len());
    let (content, _, had_err) = GBK.decode(&content);
    if had_err {
        return Err(Error::new(ErrorKind::Other, "使用GBK解碼失敗"))
    }
    println!("{}", content.len());
    println!("content: {content:?}");
    Ok(())
}

字符串處理

字符串的操作,大家可以直接查閱官方文檔,這里就不一一列舉它有的工作方法了,參考文檔: https://doc.rust-lang.org/std/string/struct.String.html

正則表達(dá)式

正則表達(dá)式很多時候還是很好用的,特別是匹配文本和獲取特定的模式字段,這里還是匹配Nginx的訪問日志記錄,數(shù)據(jù)樣本如下。

172.17.0.1 - - [20/Dec/2023:01:37:27 +0000] "GET /hello HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"

這需要依賴第三方庫regex, 可通過cargo add regex命令添加。

假設(shè)我們想獲取/hello這個字符串。

use regex::Regex;

fn main() {
    let log = r#"172.17.0.1 - - [20/Dec/2023:01:37:27 +0000] "GET /hello HTTP/1.1" 200 612 "-" "curl/7.29.0" "-""#;
    let pattern = Regex::new(r#".+?"GET\s+(.+)\s+HTTP.+?"#).unwrap();
    // 判斷是否匹配
    if pattern.is_match(log) {
        println!("該日志匹配正則表達(dá)式")
    } else {
        panic!("無法匹配正則表達(dá)式")
    }

    // 獲取匹配的部分
    if let Some(caps) = pattern.captures(log) {
        println!("{caps:?}");
        let uri = caps.get(1).unwrap().as_str();
        println!("uri: {uri}");
    } else {
        panic!("無法捕獲表達(dá)式里的內(nèi)容")
    }
}

輸出結(jié)果如下:

該日志匹配正則表達(dá)式
Captures({0: 0..61/"172.17.0.1 - - [20/Dec/2023:01:37:27 +0000] \"GET /hello HTTP/", 1: 49..55/"/hello"})
uri: /hello

如果你看不懂我寫的那串正則表達(dá)式,我覺得也沒關(guān)系,因?yàn)檫@東西需要額外的學(xué)習(xí)。因?yàn)檎齽t表達(dá)式的性能不好預(yù)測(針對長文本的時候),所以盡可能的還是用比較好理解的各種字符串方法來獲取所需要的字段吧,如果可以的話。

用展開宏處理嵌套結(jié)構(gòu)

前面在獲取Caddy的uri字段的時候,因?yàn)椴辉谧钔鈱樱孕枰扰袛鄏equest字段在不在,然后再判斷request的值里面有沒有uri字段,這還只是在第二層,如果是更加深的層次,那么需要寫很多的無聊代碼,這實(shí)在是無趣的事情,所以我們可以將這種有著相同模式的代碼用rust聲明宏來完成。

use serde_json::json;

macro_rules! serde_get {
    ($value: ident, $first: expr) => {
        {
            match ($value).get($first) {
                Some(val) => Some(val),
                None => {
                    None
                }
            }
        }
    };

    ($value: ident, $first: expr, $($others:expr)+) => {
        {
            match ($value).get($first) {
                Some(val) => {
                    serde_get!(val, $($others)+)
                },
                None => {
                    None
                }
            }
        }
    };
    // 使用聲明宏處理遞歸調(diào)用的關(guān)鍵在于$($others:tt)*
    ($value: ident, $first: expr, $($others:tt)* ) => { 
        {
            match ($value).get($first) {
                Some(val) => {
                    serde_get!(val, $($others)+)
                }
                None => None
            }
        }
    };
    
}


fn main() {
    let object = json!({
        "key11": {"key12": "key13"},
        "key21": {"key22": {"key23": "key24"}}
    });
    
    if let None = serde_get!(object, "xx") {
        println!("不存在鍵xx");
    }

    if let Some(val) = serde_get!(object, "key11", "key12") {
        println!(r#"object["key11"]["key12"] = {val:}"#);
    }

    if let Some(val) = serde_get!(object, "key21", "key22", "key23") {
        println!(r#"object["key21"]["key21"]["key23"] = {val:}"#);
    }

    if let Some(val) = serde_get!(object, "key21", "key22", "key23", "key24") {
        println!(r#"object["key21"]["key21"]["key23"]["key33"] = {val:}"#);
    } else {
        println!(r#"object["key21"]["key21"]["key23"]["key33"]不存在"#);
    }
}

代碼的輸出結(jié)果如下:

不存在鍵xx
object["key11"]["key12"] = "key13"
object["key21"]["key21"]["key23"] = "key24"
object["key21"]["key21"]["key23"]["key33"]不存在

除了使用聲明宏也可以使用遞歸函數(shù),這就看大家的喜好了。如果大家看得不是太懂,可以搜索關(guān)鍵字rust TT muncher或者rust 標(biāo)記樹撕咬機(jī) 。
這個例子寫完,我才發(fā)現(xiàn)serde_json可以直接使用["key21"]["key21"]["key23"]這樣的語法直接判斷!!!, 不過serde_json的返回結(jié)果都是null, 如果鍵值對不存在的話。

總結(jié)

說實(shí)話,就處理文本數(shù)據(jù)這塊,我感覺rust的體驗(yàn)遠(yuǎn)遠(yuǎn)比不上動態(tài)類型的編程語言,比如Python, 但是為了開發(fā)的一致性,我還是會很多情況使用Rust,在本文稍微提及了一下rust的宏編程,下一篇文章是關(guān)于聲明函的教程, 有興趣的可以關(guān)注一下。

參考鏈接:

https://github.com/serde-rs/json
https://docs.rs/encoding_rs/latest/encoding_rs/
https://docs.rs/regex/latest/regex/
https://earthly.dev/blog/rust-macros/
https://youerning.top/post/rust/rust-text-processing-tutorial/

到此這篇關(guān)于Rust文本處理快速入門 的文章就介紹到這了,更多相關(guān)Rust文本處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何使用rust實(shí)現(xiàn)簡單的單鏈表

    如何使用rust實(shí)現(xiàn)簡單的單鏈表

    實(shí)現(xiàn)單鏈表在別的語言里面可能是一件簡單的事情,單對于Rust來說,絕對不簡單,下面這篇文章主要給大家介紹了關(guān)于如何使用rust實(shí)現(xiàn)簡單的單鏈表的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-03-03
  • Rust Atomics and Locks并發(fā)基礎(chǔ)理解

    Rust Atomics and Locks并發(fā)基礎(chǔ)理解

    這篇文章主要為大家介紹了Rust Atomics and Locks并發(fā)基礎(chǔ)理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • rust中async/await的使用示例詳解

    rust中async/await的使用示例詳解

    在Rust中,async/await用于編寫異步代碼,使得異步操作更易于理解和編寫,通過使用await,在async函數(shù)或代碼塊中等待Future完成,而不會阻塞線程,允許同時執(zhí)行其他Future,這種機(jī)制簡化了異步編程的復(fù)雜性,使代碼更加直觀
    2024-10-10
  • Rust 能夠取代 C 語言嗎

    Rust 能夠取代 C 語言嗎

    Rust 是 Mozilla 基金會的一個雄心勃勃的項目,號稱是 C 語言和 C++ 的繼任者,這篇文章主要介紹了Rust 能夠取代 C 語言嗎的相關(guān)知識,需要的朋友可以參考下
    2020-06-06
  • vscode搭建rust開發(fā)環(huán)境的圖文教程

    vscode搭建rust開發(fā)環(huán)境的圖文教程

    本文主要介紹了vscode搭建rust開發(fā)環(huán)境的圖文教程,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • Rust調(diào)用Windows API 如何獲取正在運(yùn)行的全部進(jìn)程信息

    Rust調(diào)用Windows API 如何獲取正在運(yùn)行的全部進(jìn)程信息

    本文介紹了如何使用Rust調(diào)用WindowsAPI獲取正在運(yùn)行的全部進(jìn)程信息,通過引入winapi依賴并添加相應(yīng)的features,可以實(shí)現(xiàn)對不同API集的調(diào)用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • rust的package,crate,module示例解析

    rust的package,crate,module示例解析

    rust提供了非常優(yōu)秀的包管理器cargo,我們可以使用crate,module,package來組織代碼,這篇文章主要介紹了rust的package,crate,module相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • Rust裸指針的安全性實(shí)例講解

    Rust裸指針的安全性實(shí)例講解

    裸指針是一個不包含所有權(quán)和借用關(guān)系的原始指針,它們與常規(guī)指針相比沒有任何限制和保護(hù)措施,這篇文章主要介紹了Rust裸指針的安全性實(shí)例,需要的朋友可以參考下
    2023-05-05
  • Rust 函數(shù)詳解

    Rust 函數(shù)詳解

    函數(shù)在 Rust 語言中是普遍存在的。Rust 支持多種編程范式,但更偏向于函數(shù)式,函數(shù)在 Rust 中是“一等公民”,函數(shù)可以作為數(shù)據(jù)在程序中進(jìn)行傳遞,對Rust 函數(shù)相關(guān)知識感興趣的朋友一起看看吧
    2021-11-11
  • 深入理解 Rust 中的模式匹配語法(最新推薦)

    深入理解 Rust 中的模式匹配語法(最新推薦)

    Rust中的模式匹配提供了多種方式來處理不同的數(shù)據(jù)類型和場景,本文給大家介紹Rust 中的模式匹配語法,感興趣的朋友一起看看吧
    2025-03-03

最新評論