無論是Unix-like作業系統(如Linux、macOS)或是Windows作業系統的檔案路徑,皆有支援點.與點點..的功能。.被用來表示「目前路徑的節點」或是「目前的工作目錄」,例如/path/./to或是./path/to..則被用來表示「目前路徑的節點的上一層節點」或是「目前的工作目錄的上一層目錄」,例如/path/../to或是../path/to。在程式處理的過程中,或是需要輸出一些Log資訊的時候,我們也許會需要去有效地解析路徑中的...,目的是使路徑更簡單,就能更容易處理與辨別。而在這個單純情況下,我們不一定是要將該路徑轉成「絕對路徑」,也可能會希望被移除...後的路徑同樣能夠是「相對路徑」。



這篇文章中,介紹了使用Rust程式語言標準函式庫提供的canonicalize函數或是「Path Absolutize」套件的方式,來將一個路徑的...有效移除並轉成絕對路徑。如果我們並不想強制將路徑轉成絕對路徑,則可以使用「Path Dedot」這個套件。

Path Dedot

「Path Dedot」是筆者開發的套件,可以單純有效地解析檔案路徑中的.或是..。事實上,「Path Absolutize」套件就是使用「Path Dedot」套件來做...的解析。

Crates.io

Cargo.toml

path-dedot = "*"

使用方法

使用use關鍵字來將path_dedot這個crate底下的ParseDot特性給引用到當前的程式範圍下,PathPathBuf結構體就會擁有parse_dot方法了!這個方法可以將路徑轉換成沒有...路徑。

轉換規則如下:

  • 如果路徑不是以...開頭,則.會被忽略,..表示為上一層節點。

    use std::path::Path;
    
    use path_dedot::*;
    
    let p = Path::new("/path/to/../123/456/./777/..");
    
    assert_eq!("/path/123/456", p.parse_dot().unwrap().to_str().unwrap());
  • 如果路徑是以...開頭,則.表示目前程式的工作目錄路徑,..表示目前的工作目錄的上一層目錄。如果目前的工作目錄是根目錄,其上一層目錄也依然還是根目錄。

    use std::path::Path;
    use std::env;
    
    use path_dedot::*;
    
    let p = Path::new("../path/to/123/456");
    
    let cwd = env::current_dir().unwrap();
    
    let cwd_parent = cwd.parent();
    
    match cwd_parent {
       Some(cwd_parent) => {
          assert_eq!(Path::join(&cwd_parent, Path::new("path/to/123/456")).to_str().unwrap(), p.parse_dot().unwrap().to_str().unwrap());
       }
       None => {
          assert_eq!(Path::join(Path::new("/"), Path::new("path/to/123/456")).to_str().unwrap(), p.parse_dot().unwrap().to_str().unwrap());
       }
    }
  • 原路徑如果是一個不是以...開頭的相對路徑,不論原路徑中的..有多少個,轉換出來的路徑均不會向上超出這個原路徑。

    use std::path::Path;
    
    use path_dedot::*;
    
    let p = Path::new("path/to/../../../../123/456/./777/..");
    
    assert_eq!("123/456", p.parse_dot().unwrap().to_str().unwrap());
  • 原路徑如果是有包含..的絕對路徑,不論原路徑中的..有多少個,轉換出來的絕對路徑均不會向上超出根目錄。

    use std::path::Path;
    
    use path_dedot::*;
    
    let p = Path::new("/path/to/../../../../123/456/./777/..");
    
    assert_eq!("/123/456", p.parse_dot().unwrap().to_str().unwrap());
快取

預設的情況下,每次parse_dot方法在執行的時候,都會去建立新的PathBuf實體來表示目前的工作目錄。這樣的作法會有明顯的開支。雖然這樣可以讓我們在程式執行階段於任意時間點安全地讓程式本身(例如使用std::env::set_current_dir方法)或是藉由外部控制(例如使用gdb去呼叫chdir)去修改程式目前的工作目錄,但在大多數的情況下我們並不需要這樣的功能。

為了讓路徑的解析更有效率,這個crate還提供了三種方式來快取目前的工作目錄。

once_cell_cache

啟用once_cell_cache特色可以讓這個crate使用once_cell來快取目前的工作目錄。這種快取方式是執行緒安全的(thread-safe),但一旦目前的工作目錄被快取了,在程式執行階段之後就無法再被改變了。

[dependencies.path-dedot]
version = "*"
features = ["once_cell_cache"]
lazy_static_cache

啟用lazy_static_cache特色可以讓這個crate使用lazy_static來快取目前的工作目錄。這種快取方式是執行緒安全的(thread-safe),但一旦目前的工作目錄被快取了,在程式執行階段之後就無法再被改變了。

[dependencies.path-dedot]
version = "*"
features = ["lazy_static_cache"]
unsafe_cache

啟用unsafe_cache特色可以讓這個crate使用一個可變的全域靜態變數來快取目前的工作目錄。這種快取方式允許程式可以程式本身去改變程式目前的工作目錄,但並不是執行緒安全的。

您需要使用update_cwd函數來初始化目前的工作目錄,這個函數也應該要在目前的工作目錄被改變後被呼叫。

[dependencies.path-dedot]
version = "*"
features = ["unsafe_cache"]
use std::path::Path;

use path_dedot::*;

unsafe {
    update_cwd();
}

let p = Path::new("./path/to/123/456");

println!("{}", p.parse_dot().unwrap().to_str().unwrap());

std::env::set_current_dir("/").unwrap();

unsafe {
    update_cwd();
}

println!("{}", p.parse_dot().unwrap().to_str().unwrap());
Path Dedot有跨作業系統嗎?

上面貌似都是以Unix-like作業系統(如Linux、macOS)的檔案路徑來舉例,那如果是要用在Windows作業系統呢?別擔心,「Path Dedot」也是有支援Windows作業系統的,使用方式也完全一樣!例如:

use std::path::Path;
use std::env;
    
use path_dedot::*;

let p = Path::new(r".\path\to\123\456");
	
assert_eq!(Path::join(env::current_dir().unwrap().as_path(), Path::new(r"path\to\123\456")).to_str().unwrap(), p.parse_dot().unwrap().to_str().unwrap());