転回
『Unix/Linuxプログラミング 理論と実践』をダシにしてRustのお勉強、システムプログラミングよりのPythonのお勉強をしようと思い立ったものの、『Unix/Linuxプログラミング 理論と実践』の who
コマンド実装でmacOSとDocker上のUbuntu Linuxの違いで疲れ果ててしまった。
また、いい本だと聞いて積ん読になっていた『 Linuxプログラミングインタフェース』には who
の参照元であった utmpx
構造体について詳しく書いてあるなどうれしいことが多かったので、転回して『 Linuxプログラミングインタフェース』を読もうと思い立った。
概要
4章からシステムコールAPIの説明が始まり、最初はファイルIOから説明される。
UNIXシステムではデバイスもファイルの一種として扱うことができるので、ファイルの取り扱いは重要かつ基礎となる。
ファイルを開くとそのファイルにファイルディスクリプタを割り当てる。
ファイルIOで重要になるのが4つのシステムコール、open
、read
, write
, close
である。
最初は概観を掴むために、cp
コマンドの簡易版を実装する。
Python
Pythonでは通常、ファイルを開く際は組み込み関数open()
を使うが、os
モジュールを駆使してシステムコールopen
、read
, write
, close
を使ったファイルオープンおよび処理もできる。ファイルモードに関してはstat
モジュールも援用する。
import argparse import os import stat import sys BUF_SIZE = 1024 def copy_file(src, dst): try: src_fd = os.open(src, os.O_RDONLY) except FileNotFoundError: print(f"No such file: {src}.") return os.EX_NOINPUT except PermissionError: print(f"input {src} can not open.") return os.EX_NOPERM dst_flag = os.O_CREAT | os.O_WRONLY | os.O_TRUNC dst_mode = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH ) try: dst_fd = os.open(dst, dst_flag, dst_mode) except OSError: print(f"output {dst} can not open.") return os.EX_CANTCREAT while True: read_bytes = os.read(src_fd, BUF_SIZE) if len(read_bytes) == 0: break written_bytes_len = os.write(dst_fd, read_bytes) if len(read_bytes) != written_bytes_len: print("could not write whole buffer.") return os.EX_IOERR try: os.close(src_fd) except OSError: print("cloud not close input file.") return os.EX_IOERR try: os.close(dst_fd) except OSError: print("cloud not close output file.") return os.EX_IOERR return os.EX_OK def main(): parser = argparse.ArgumentParser() parser.add_argument("source_file") parser.add_argument("target_file") args = parser.parse_args() ret_code = copy_file(args.source_file, args.target_file) sys.exit(ret_code) if __name__ == "__main__": main()
よりPythonらしくかこうと思うならば、以下の通り。
C言語でファイルを扱う際に、システムコールopen
の代わりにfopen
を使うのが自然であるのと同様に、Pythonでファイルを扱う際はos.open
ではなくopen
を使うのが自然である。
また、with
文を使えば自動的にファイルを閉じることができるので記述量が減り、かつ安心して書くことができる。
import argparse import os import sys import stat BUF_SIZE = 1024 def copy_file(src, dst): with open(src, "rb") as src_f, open(dst, "wb") as dst_f: while True: read_bytes = src_f.read(BUF_SIZE) if len(read_bytes) == 0: break try: written_bytes_len = dst_f.write(read_bytes) except OSError: print("could not write whole buffer.") return os.EX_IOERR if len(read_bytes) != written_bytes_len: print("could not write whole buffer.") return os.EX_IOERR try: dst_mode = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH ) os.chmod(dst, dst_mode) except OSError: print("could not change mode.") return os.EX_IOERR return os.EX_OK def main(): parser = argparse.ArgumentParser() parser.add_argument("source_file") parser.add_argument("target_file") args = parser.parse_args() ret_code = copy_file(args.source_file, args.target_file) sys.exit(ret_code) if __name__ == "__main__": main()
Rust
RustのIO周りはRead
, BufRead
, Write
という3つのトレイトが基本となる。
closeに関しては多くのreader, writerが自動で閉じるように実装されているらしい。
以下の実装は『プログラミングRust』P.420の実装を大いに参考にしている。
use std::io::Write; const BUF_SIZE: usize = 8 * 1024; fn copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> std::io::Result<u64> where R: std::io::Read, W: std::io::Write, { let mut buf = [0; BUF_SIZE]; let mut written = 0; loop { let len = match reader.read(&mut buf) { Ok(0) => return Ok(written), Ok(len) => len, Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue, Err(e) => return Err(e), }; writer.write_all(&buf[..len])?; written += len as u64; } } fn main() -> std::io::Result<()> { let mut _args = Vec::new(); for arg in std::env::args().skip(1) { _args.push(arg); } if _args.len() != 2 { writeln!(std::io::stderr(), "Usage: copy source_file target_file").unwrap(); } else { // let source_file_path = Path::new(&_args[0]); // let target_file_path = Path::new(&_args[1]); // std::fs::copy(source_file_path, target_file_path)?; let mut source_file = std::fs::File::open(&_args[0])?; let mut target_file = std::fs::File::create(&_args[1])?; copy(&mut source_file, &mut target_file)?; } Ok(()) }
なお、コメントにある通り、ファイルをコピーするならばstd::fs::copy
を使うのが普通のようである。