LoginSignup
7
3

More than 1 year has passed since last update.

Rustで簡易lsコマンドを作る

Posted at

はじめに

Rustでファイルの一覧を表示させる簡単なコマンドラインツールを開発しました。
隠しファイルも含め全てのファイルを表示させます。
引数として受け取れるのはパスだけでオプションはありません。

使用例

ツール名はrlsにしました。
引数なしの場合はカレントディレクトリになります。

$ ./rls 
.cargo-lock
.fingerprint
build
deps
examples
incremental
rls
rls.d

~でホームディレクトリを指定できます
ファイル数が多いので一部省略しています。

$ ./rls ~/
.bash_history
.bash_logout
...
Videos

解説

実装した処理について簡単に解説します。

dirsクレートを使用

ホームディレクトリを取得するにはstd::env::home_dirを使用すればよいですが、この関数は現在非推奨です。

warning: use of deprecated function `std::env::home_dir`: This function's behavior is unexpected and probably not what you want. Consider using a crate from crates.io instead.

代わりにdirsクレートにあるdirs::home_dirを使用します。
Cargo.tomlに以下を追記します。

[dependencies]
dirs = "4.0.0"

パスの文字列を解析する

パスの文字列を解析しPathBufとして返す関数を追加します。
先頭が~の場合はホームディレクトリのパスに置き換えます。

fn parse_path(path: &str) -> PathBuf {
    if path.starts_with("~") {
        let mut home_dir = dirs::home_dir().unwrap();
        path[1..].split("/").for_each(|f| home_dir.push(f));
        return home_dir;
    }
    PathBuf::from(path)
}

ファイルの一覧を取得する

指定したディレクトリ内のファイルの一覧を返す関数を追加します。
一覧をファイル名でソートしてから返します。

フォルダが見つからない、またはディレクトリ以外が指定されていた場合はエラーメッセージを返します。

fn get_dir_entries(dir: &PathBuf) -> Result<Vec<DirEntry>, String> {
    if !dir.exists() {
        let mes = format!("{} が見つかりませんでした。", dir.to_str().unwrap());
        return Err(mes);
    }
    if !dir.is_dir() {
        let mes = format!("{} はディレクトリではありません。", dir.to_str().unwrap());
        return Err(mes);
    }
    let mut entries: Vec<DirEntry> = fs::read_dir(dir).unwrap().filter_map(|f| f.ok()).collect();
    entries.sort_by_key(|e| e.file_name());
    Ok(entries)
}

ファイル名を取得する

DirEntryからファイル名を取得する関数を追加します。
OsStringStringに変換します。

fn get_file_name(entry: &DirEntry) -> String {
    entry.file_name().into_string().unwrap()
}

OsStringとは

プラットフォーム依存の文字列型です。
通常のStringはUTF-8ですがOsStringのエンコーディングはOSによって異なります。
公式のドキュメントによるとUnix系OSの場合はUTF-8で、Windowsの場合はUTF-16です。

コード

最後にコード全体を載せます。

コード
use dirs;
use std::{
    env,
    fs::{self, DirEntry},
    path::PathBuf,
    process,
};

fn parse_path(path: &str) -> PathBuf {
    if path.starts_with("~") {
        let mut home_dir = dirs::home_dir().unwrap();
        path[1..].split("/").for_each(|f| home_dir.push(f));
        return home_dir;
    }
    PathBuf::from(path)
}

fn get_dir_entries(dir: &PathBuf) -> Result<Vec<DirEntry>, String> {
    if !dir.exists() {
        let mes = format!("{} が見つかりませんでした。", dir.to_str().unwrap());
        return Err(mes);
    }
    if !dir.is_dir() {
        let mes = format!("{} はディレクトリではありません。", dir.to_str().unwrap());
        return Err(mes);
    }
    let mut entries: Vec<DirEntry> = fs::read_dir(dir).unwrap().filter_map(|f| f.ok()).collect();
    entries.sort_by_key(|e| e.file_name());
    Ok(entries)
}

fn get_file_name(entry: &DirEntry) -> String {
    entry.file_name().into_string().unwrap()
}

fn main() {
    let args: Vec<String> = env::args().skip(1).collect();
    let path = if args.len() == 0 { "." } else { &args[0] };
    let dir = parse_path(path);
    match get_dir_entries(&dir) {
        Ok(entries) => {
            entries
                .iter()
                .for_each(|e| println!("{}", get_file_name(e)));
        }
        Err(mes) => {
            println!("{}", mes);
            process::exit(1);
        }
    }
}

参考文献

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3