1対1対応の演習で例題しかやらなかったタイプ
『 Linuxプログラミングインタフェース』4章の演習としてtee
コマンドの実装をする。
tee
コマンドは標準入力をEOFまで受け取り、標準出力と指定したファイルに書き出すコマンドである。
デフォルト動作は、指定したファイルを新規作成として扱うが、-a
フラグを渡した際は追記にする。
要は出力を枝分かれさせるコマンドである。
必要なパーツとして4章で学んだ(気分になっている)open
でいい感じにフラグを指定してファイルを開いて、read
やwrite
、時には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++で足を打ち抜いた経験がないからだろうか。