何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『 Linuxプログラミングインタフェース』を気合いで読む会 第3回:ファイルIO演習

1対1対応の演習で例題しかやらなかったタイプ

Linuxプログラミングインタフェース』4章の演習としてteeコマンドの実装をする。 teeコマンドは標準入力をEOFまで受け取り、標準出力と指定したファイルに書き出すコマンドである。 デフォルト動作は、指定したファイルを新規作成として扱うが、-aフラグを渡した際は追記にする。 要は出力を枝分かれさせるコマンドである。 必要なパーツとして4章で学んだ(気分になっている)openでいい感じにフラグを指定してファイルを開いて、readwrite、時にはseekを使って書けるはずである。

Python

  • 引数-aの有無に応じてos.open()渡す引数を変える。
  • 標準入力や標準出力に対して読み書きをする場合はsys.stdin.buffer.read()sys.stdout.buffer.write()とする。
  • ヘルプの文言はGNUのmanコマンドの文章から引用した。
import argparse
import os
import sys
import stat


def tee(file, is_append):
    open_flags = os.O_WRONLY | os.O_CREAT
    if is_append:
        open_flags |= os.O_APPEND
    else:
        open_flags |= os.O_TRUNC
    open_mode = (
        stat.S_IRUSR
        | stat.S_IWUSR
        | stat.S_IRGRP
        | stat.S_IWGRP
        | stat.S_IROTH
        | stat.S_IWOTH
    )
    try:
        fd = os.open(file, open_flags, open_mode)
    except FileNotFoundError:
        print(f"No such file: {file}.")
        return os.EX_NOINPUT
    except PermissionError:
        print(f"input {file} can not open.")
        return os.EX_NOPERM
    s = sys.stdin.buffer.read()
    sys.stdout.buffer.write(s)

    try:
        os.write(fd, s)
    except OSError:
        print("cloud not write file.")
        return os.EX_IOERR

    try:
        os.close(fd)
    except OSError:
        print("cloud not close input file.")
        return os.EX_IOERR

    return os.EX_OK


def main():
    parser = argparse.ArgumentParser(
        description="Read from standard input and write to standard output and file."
    )
    parser.add_argument(
        "-a",
        dest="is_append",
        action="store_true",
        help="Append to the given file, do not overwrite.",
    )
    parser.add_argument(
        "file", help="Copy standard input to each file, and also to standard output."
    )
    args = parser.parse_args()
    ret_code = tee(args.file, args.is_append)
    sys.exit(ret_code)


if __name__ == "__main__":
    main()

Rust

  • コマンドライン引数の解釈に限界を感じたのでgetoptsクレートを採用した。使い方はサンプルに酷似している。
  • rust-lang-nurseryレポジトリの位置づけがよくわからない。
  • ファイルを開く際の細かいオプションはstd::fs::OpenOptionsで制御する。
  • if式の中で変数定義をするとそのスコープでした変数が使えないのでif式の外で変数を定義する。
  • IO系のクレート?はデフォルトでバッファリングが無効なので、明示的にバッファリングを行う必要がある。これがRustっぽい振る舞いなのだろう。
  • Result<()>を返す函数でとりあえず Ok(())を返すのは、例外を握りつぶしているのと同義なのではと思う。お辛い。
extern crate getopts;

use getopts::Options;
use std::env;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufReader;
use std::io::BufWriter;
use std::io::Read;
use std::io::Write;

fn tee(out: &str, is_append: bool) -> std::io::Result<()> {
    let mut output_file;
    if is_append {
        output_file = OpenOptions::new().append(true).open(out)?;
    } else {
        output_file = File::create(out)?;
    };
    let mut bufferd_output_file = BufWriter::new(output_file);

    let stdin = std::io::stdin();
    let mut bufferd_stdin = BufReader::new(stdin.lock());
    let mut stdin_payload = Vec::new();
    bufferd_stdin.read_to_end(&mut stdin_payload)?;

    let stdout = std::io::stdout();
    let mut stdout_buffer = BufWriter::new(stdout.lock());

    stdout_buffer.write(&stdin_payload)?;
    bufferd_output_file.write(&stdin_payload)?;
    Ok(())
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options] FILE", program);
    print!("{}", opts.usage(&brief));
}

fn main() -> std::io::Result<()> {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflag("a", "append", "append to the given file, do not overwrite.");
    opts.optflag("h", "help", "print this help menu");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!(f.to_string()),
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return Ok(());
    }
    let is_append = matches.opt_present("a");
    let output = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        print_usage(&program, opts);
        return Ok(());
    };
    tee(&output, is_append)?;
    Ok(())
}

感想

  • Rustのありがたみがまだわかっていない。CやC++で足を打ち抜いた経験がないからだろうか。