golangの日記

Go言語を中心にプログラミングについてのブログ

treeコマンドの書き方

rust.png


treeコマンドはツーリー形式でディレクトリ構造を表示するプログラムです。
Windowsにはtreeコマンドが最初からありますが、MacOSUbuntuにはインストールされていません。
なので、インストールするか自分で書くかということで、最低限ですがRust入門がてら書いてみました。
(初めて書いたプログラムが python で tree コマンドだったと思う)





use std::fs;
use std::io;
use std::path::{Path, PathBuf};

fn tree(indent: String, dir: &Path) -> io::Result<()> {
    let mut files: Vec<PathBuf> = Vec::new();
    let mut dirs: Vec<PathBuf> = Vec::new();

    // 末尾の ? は try! のシンタックスシュガーらしい
    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();
        // ディレクトリとファイルを分ける
        // 理由としてはディレクトリが 0個だった場合に
        // 最後のファイルに └─ を使うようにするため
        if path.is_dir() {
            dirs.push(path);
        } else {
            files.push(path);
        }
    }

    // ベクターでループ回数が必要な場合は iter().enumerate() する
    for (i, v) in files.iter().enumerate() {
        let s: String;
        if dirs.len() == 0 && i == files.len() - 1 {
            s = indent.clone() + " └─"
        } else {
            s = indent.clone() + " ├─"
        }
        output(s, &v);
    }

    for (i, v) in dirs.iter().enumerate() {
        let a: &str; // 次の tree 関数の indent に追加する文字列
        if i == dirs.len() - 1 {
            a = "   ";
            output(indent.clone() + " └─", &v)
        } else {
            a = " │ ";
            output(indent.clone() + " ├─", &v)
        }
        tree(indent.clone() + a, &v)?;
    }

    Ok(())
}

fn output(indent: String, path: &PathBuf) {
    // OsString を String にする
    // WindowsではUTF-16で解釈される場合があるので、Unixとの差を埋めるためにOsStringがあるらしい
    let s = path.file_name().unwrap().to_string_lossy();
    println!("{} {}", indent, s);
}

fn main() {
    const ROOT: &str = ".";
    println!(" {}", ROOT);

    if let Err(err) = tree(String::new(), &Path::new(&ROOT)) {
        println!("Error: {}", err);
    }
}

├─└─box-drawing character というものらしいです。




同じコードをGo言語で書いた場合

package main

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
)

func readdir(name string) ([]string, []string, error) {
    fp, err := os.Open(name)
    if err != nil {
        return nil, nil, err
    }

    list, err := fp.Readdir(-1)
    fp.Close()
    if err != nil {
        return nil, nil, err
    }

    dirs, files := []string{}, []string{}
    for _, v := range list {
        if v.IsDir() {
            dirs = append(dirs, v.Name())
        } else {
            files = append(files, v.Name())
        }
    }

    return dirs, files, nil
}

func tree(indent, path string) error {
    dirs, files, err := readdir(path)
    if err != nil {
        return err
    }

    for i, v := range files {
        s := indent + " ├─"
        if len(dirs) == 0 && i == len(files)-1 {
            s = indent + " └─"
        }

        fmt.Printf("%s %s\n", s, v)
    }

    for i, v := range dirs {
        s := indent + " ├─"
        a := " │ "
        if i == len(dirs)-1 {
            s = indent + " └─"
            a = "   "
        }

        fmt.Printf("%s %s\n", s, v)

        if err := tree(indent+a, filepath.Join(path, v)); err != nil {
            return err
        }
    }

    return nil
}

func main() {
    const Root = "."

    fmt.Printf(" %s\n", Root)

    path, err := filepath.Abs(Root)
    if err != nil {
        log.Fatal(err)
    }

    if err := tree("", path); err != nil {
        log.Fatal(err)
    }
}