rust-shell - 背景

歴史的経緯

シェルの発展

  • Thompson Shell(1971年)
- 最初のUnixシェル - パイプの導入

  • Bourne Shell(1977年)
- シェルスクリプトの基礎 - 変数、制御構文

  • C Shell(1978年)
- C言語風の構文 - ヒストリー機能

  • Bash(1989年)
- Bourne Shell 互換 - 現在の標準

パイプの革命

# Ken Thompson が Doug McIlroy のアイデアを実装
# "一つのことをうまくやる" Unix 哲学の体現
cat file | grep pattern | sort | uniq

コンピュータサイエンス的な意味

プロセス生成

fork() システムコール:
1. 親プロセスを複製
2. 子プロセスを生成
3. 両方が fork() から戻る
   - 親: 子のPIDを返す
   - 子: 0を返す

exec() システムコール:
- 現在のプロセスを別のプログラムに置換
- メモリ空間を上書き

ファイルディスクリプタ

FD 名前 説明
0 stdin 標準入力
1 stdout 標準出力
2 stderr 標準エラー

リダイレクションの仕組み

// stdout をファイルにリダイレクト
let file = File::create("output.txt")?;
dup2(file.as_raw_fd(), 1);  // FD 1 (stdout) を置換

// パイプ
let (read_fd, write_fd) = pipe()?;
// 子プロセス1: stdout → write_fd
// 子プロセス2: read_fd → stdin

実践での活用

シェルスクリプトの例

#!/bin/bash
# ログファイルの解析

cat access.log |
  grep "ERROR" |
  awk '{print $4}' |
  sort |
  uniq -c |
  sort -rn |
  head -10

プロセス間通信

┌──────────┐    pipe    ┌──────────┐
│ Process1 │ ──────────→ │ Process2 │
│ stdout   │            │ stdin    │
└──────────┘            └──────────┘

実世界とのギャップ

Rustでのシステムコール

use std::process::{Command, Stdio};

// Command API は fork/exec を抽象化
let child = Command::new("ls")
    .arg("-la")
    .stdout(Stdio::piped())
    .spawn()?;

// 低レベル操作には nix クレート
use nix::unistd::{fork, ForkResult};
match unsafe { fork() } {
    Ok(ForkResult::Parent { child }) => { /* 親 */ }
    Ok(ForkResult::Child) => { /* 子 */ }
    Err(_) => { /* エラー */ }
}

シェルの複雑さ

# 本物のシェルが処理すべきもの:
echo "hello world"           # クォート処理
echo $HOME                   # 変数展開
echo $(date)                 # コマンド置換
ls *.txt                     # グロブ展開
cmd1 && cmd2 || cmd3         # 論理演算子
if [ -f file ]; then ...; fi # 制御構文