第4章: プロセス実行モデルとコマンド実行

4.1 UNIXプロセスモデルの理論

プロセスの概念

プロセス(Process)は、実行中のプログラムのインスタンスである。Dennis RitchieとKen Thompsonは、"The UNIX Time-Sharing System"(Communications of the ACM, 1974)において、UNIXのプロセスモデルを定義した。

プロセスの構成要素

  • テキストセグメント: 実行可能コード
  • データセグメント: 初期化されたグローバル変数
  • BSSセグメント: 未初期化のグローバル変数
  • ヒープ: 動的に割り当てられたメモリ
  • スタック: 関数呼び出しとローカル変数
  • プロセス制御ブロック(PCB): カーネルが管理するメタデータ

プロセスのメモリレイアウト:

高位アドレス
┌─────────────────┐
│     スタック      │ ↓ 成長方向
├─────────────────┤
│        ↓        │
│     (空き領域)   │
│        ↑        │
├─────────────────┤
│      ヒープ      │ ↑ 成長方向
├─────────────────┤
│       BSS       │
├─────────────────┤
│     データ       │
├─────────────────┤
│    テキスト      │
└─────────────────┘
低位アドレス

fork-execモデル

UNIXの革新的な設計の1つがfork-execモデルである。

fork() - プロセスの複製:

  • 呼び出しプロセス(親)の完全なコピーを作成
  • 親子は独立したアドレス空間を持つ
  • 戻り値で親子を区別(親: 子のPID、子: 0)

exec() - プロセスイメージの置換:

  • 現在のプロセスを新しいプログラムで置き換える
  • PIDは変わらない
  • 成功時は戻らない

Ken Thompsonはこの分離設計の理由を説明している:

> "The separation of fork and exec allows the shell to set up I/O redirection and pipelines between the fork and the exec." > (forkとexecの分離により、シェルはforkとexecの間でI/Oリダイレクションとパイプラインを設定できる)

コピーオンライト(Copy-on-Write)

初期のUNIXでは、fork()は親のメモリ全体を物理的にコピーしていた。これは非効率であった。

現代のUNIXはコピーオンライト(COW: Copy-on-Write)を実装する:

  • fork()時、親子はメモリページを共有
  • ページは読み取り専用としてマーク
  • いずれかが書き込むと、そのページのみコピー

fork()直後:

親プロセス                    子プロセス
ページテーブル                ページテーブル
    │                           │
    └────────┐   ┌──────────────┘
             ↓   ↓
         ┌────────────┐
         │ 物理メモリ  │ (共有)
         └────────────┘

書き込み後:

親プロセス                    子プロセス
ページテーブル                ページテーブル
    │                           │
    ↓                           ↓
┌────────┐                  ┌────────┐
│ 物理A   │                 │ 物理B   │
└────────┘                  └────────┘
(変更されたページ)          (コピー)

4.2 システムコールインターフェース

POSIX標準

POSIX.1(IEEE Std 1003.1)は、UNIXライクなシステムのためのポータブルなインターフェースを定義する。

プロセス管理に関連する主要なシステムコール:

| システムコール | 説明 | |--------------|------| | fork() | プロセスの複製 | | execve() | プログラムの実行 | | wait() | 子プロセスの終了を待つ | | waitpid() | 特定の子プロセスを待つ | | exit() | プロセスの終了 | | getpid() | プロセスIDの取得 | | getppid() | 親プロセスIDの取得 |

fork()の詳細

#include <unistd.h>

pid_t fork(void);

戻り値

  • 親プロセス:子のPID(正の整数)
  • 子プロセス:0
  • エラー:-1(errnoが設定される)

fork()で継承されるもの

  • メモリ内容(COWにより共有)
  • 開いているファイルディスクリプタ
  • 環境変数
  • シグナルハンドラ
  • プロセスグループID

fork()で継承されないもの

  • PID(新しいPIDが割り当てられる)
  • PPIDは呼び出し元のPIDになる
  • ファイルロック
  • ペンディングシグナル

execve()の詳細

#include <unistd.h>

int execve(const char *pathname, char *const argv[], char *const envp[]);

パラメータ

  • pathname: 実行ファイルのパス
  • argv: 引数配列(NULL終端)
  • envp: 環境変数配列(NULL終端)

exec関数ファミリ

| 関数 | パス | 引数 | 環境 | |------|------|------|------| | execl | パス | リスト | 継承 | | execle | パス | リスト | 明示 | | execlp | PATH検索 | リスト | 継承 | | execv | パス | 配列 | 継承 | | execve | パス | 配列 | 明示 | | execvp | PATH検索 | 配列 | 継承 |

命名規則:

  • l: 引数をリスト(variadic)で指定
  • v: 引数をベクタ(配列)で指定
  • e: 環境変数を明示的に指定
  • p: PATH環境変数を使用して検索

wait()とwaitpid()

#include <sys/wait.h>

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

waitpid()のpidパラメータ

  • > 0: 指定されたPIDの子を待つ
  • 0: 同じプロセスグループの任意の子を待つ
  • -1: 任意の子を待つ(wait()と等価)
  • < -1: プロセスグループID == |pid| の任意の子を待つ
  • statusの解析マクロ

    WIFEXITED(status)   /* 正常終了した場合に真 */
    WEXITSTATUS(status) /* 終了ステータス(WIFEXITED時のみ有効) */
    WIFSIGNALED(status) /* シグナルで終了した場合に真 */
    WTERMSIG(status)    /* 終了させたシグナル番号 */
    WCOREDUMP(status)   /* コアダンプした場合に真 */
    WIFSTOPPED(status)  /* 停止した場合に真 */
    WSTOPSIG(status)    /* 停止させたシグナル番号 */
    

    4.3 ファイルディスクリプタとリダイレクション

    ファイルディスクリプタの理論

    ファイルディスクリプタ(File Descriptor)は、開いているファイルへの参照を表す非負整数である。

    UNIXカーネルは3つのデータ構造を管理する:

  • プロセスファイルテーブル: プロセスごとのfd配列
  • オープンファイルテーブル: システム全体のファイル状態
  • vノードテーブル: ファイルシステムのメタデータ
  • プロセスA          オープンファイルテーブル     vノードテーブル
    ┌────────┐        ┌─────────────────┐        ┌───────────┐
    │ fd 0 ──┼───────→│ オフセット: 100 │───────→│ ファイルA │
    │ fd 1 ──┼──┐     │ フラグ: O_RDONLY│        └───────────┘
    │ fd 2 ──┼─┐│     └─────────────────┘
    └────────┘ ││     ┌─────────────────┐        ┌───────────┐
               │└────→│ オフセット: 0   │───────→│ ファイルB │
               │      │ フラグ: O_WRONLY│        └───────────┘
               │      └─────────────────┘
               │      ┌─────────────────┐        ┌───────────┐
               └─────→│ オフセット: 0   │───────→│ ファイルC │
                      │ フラグ: O_WRONLY│        └───────────┘
                      └─────────────────┘
    

    標準ファイルディスクリプタ

    POSIXは3つの標準ファイルディスクリプタを定義:

  • 0 (STDIN_FILENO): 標準入力
  • 1 (STDOUT_FILENO): 標準出力
  • 2 (STDERR_FILENO): 標準エラー出力

dup()とdup2()

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);

dup(): oldfdを複製し、最小の利用可能なfdを返す

dup2(): oldfdnewfdに複製する

  • newfdが開いていれば先に閉じる
  • oldfd == newfdの場合、何もしない
  • リダイレクションの実装に不可欠:

    /* stdout をファイルにリダイレクト */
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    dup2(fd, STDOUT_FILENO);  /* fd 1 を fd に向ける */
    close(fd);                 /* 元のfdは不要 */
    

    4.4 PATH検索アルゴリズム

    PATH環境変数

    PATH環境変数は、実行可能ファイルを検索するディレクトリのリストを指定する:

    PATH=/usr/local/bin:/usr/bin:/bin
    

    検索アルゴリズム

    POSIXが定義するコマンド検索手順:

  • コマンドが/を含む場合:
- そのパスを直接使用

  • コマンドが/を含まない場合:
- PATHの各ディレクトリを順に検索 - 最初に見つかった実行可能ファイルを使用

/**
 * コマンドのパスを検索する
 * @param cmd コマンド名
 * @param env 環境変数リスト
 * @return 実行可能ファイルのパス、見つからない場合はNULL
 */
char    *find_command_path(const char *cmd, t_env *env)
{
    char    *path_env;
    char    **paths;
    char    *full_path;
    int     i;

    /* 空のコマンドはエラー */
    if (!cmd || !*cmd)
        return (NULL);

    /* 絶対パスまたは相対パス */
    if (ft_strchr(cmd, '/'))
        return (validate_path(cmd));

    /* PATH環境変数を取得 */
    path_env = env_get(env, "PATH");
    if (!path_env)
        return (NULL);

    /* ':' で分割 */
    paths = ft_split(path_env, ':');
    if (!paths)
        return (NULL);

    /* 各ディレクトリを検索 */
    i = 0;
    while (paths[i])
    {
        full_path = path_join(paths[i], cmd);
        if (access(full_path, X_OK) == 0)
        {
            free_array(paths);
            return (full_path);
        }
        free(full_path);
        i++;
    }

    free_array(paths);
    return (NULL);
}

/**
 * パスを検証する
 */
char    *validate_path(const char *path)
{
    struct stat st;

    /* 存在確認 */
    if (access(path, F_OK) != 0)
    {
        print_error(path, "No such file or directory");
        return (NULL);
    }

    /* ディレクトリでないことを確認 */
    if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
    {
        print_error(path, "Is a directory");
        return (NULL);
    }

    /* 実行権限確認 */
    if (access(path, X_OK) != 0)
    {
        print_error(path, "Permission denied");
        return (NULL);
    }

    return (ft_strdup(path));
}

4.5 コマンド実行の実装

単純コマンドの実行

/**
 * 単純コマンドを実行する
 * @param ast コマンドのASTノード
 * @param shell シェル状態
 * @return 終了ステータス
 */
int execute_command(t_ast *ast, t_shell *shell)
{
    pid_t   pid;
    int     status;
    char    *path;
    char    **envp;

    /* ビルトインコマンドの確認 */
    if (is_builtin(ast->argv[0]))
        return (execute_builtin(ast, shell));

    /* コマンドパスの検索 */
    path = find_command_path(ast->argv[0], shell->env);
    if (!path)
        return (127);  /* command not found */

    /* 環境変数配列の作成 */
    envp = env_to_array(shell->env);
    if (!envp)
    {
        free(path);
        return (1);
    }

    /* 子プロセスを生成 */
    pid = fork();
    if (pid == -1)
    {
        perror("minishell: fork");
        free(path);
        free_array(envp);
        return (1);
    }

    if (pid == 0)
        execute_child(ast, path, envp);
    else
        status = wait_for_child(pid);

    free(path);
    free_array(envp);
    return (status);
}

子プロセスの処理

/**
 * 子プロセスでコマンドを実行する
 * この関数は戻らない
 */
void    execute_child(t_ast *ast, char *path, char **envp)
{
    /* シグナルをデフォルトに戻す */
    signal(SIGINT, SIG_DFL);
    signal(SIGQUIT, SIG_DFL);

    /* リダイレクトを設定 */
    if (setup_redirections(ast->redirects) != 0)
        exit(1);

    /* コマンドを実行 */
    execve(path, ast->argv, envp);

    /* execveが失敗した場合 */
    perror("minishell: execve");
    exit(126);
}

/**
 * 子プロセスの終了を待つ
 */
int wait_for_child(pid_t pid)
{
    int status;

    waitpid(pid, &status, 0);

    if (WIFEXITED(status))
        return (WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
    {
        /* シグナルで終了した場合、128 + シグナル番号 */
        return (128 + WTERMSIG(status));
    }

    return (1);
}

リダイレクトの設定

/**
 * リダイレクトを設定する
 * @param redirects リダイレクトリスト
 * @return 成功時0、失敗時-1
 */
int setup_redirections(t_redirect *redirects)
{
    t_redirect  *current;

    current = redirects;
    while (current)
    {
        if (apply_redirect(current) != 0)
            return (-1);
        current = current->next;
    }
    return (0);
}

/**
 * 単一のリダイレクトを適用する
 */
int apply_redirect(t_redirect *redir)
{
    int fd;

    if (redir->type == REDIR_INPUT)
    {
        fd = open(redir->file, O_RDONLY);
        if (fd == -1)
            return (print_open_error(redir->file));
        dup2(fd, STDIN_FILENO);
        close(fd);
    }
    else if (redir->type == REDIR_OUTPUT)
    {
        fd = open(redir->file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd == -1)
            return (print_open_error(redir->file));
        dup2(fd, STDOUT_FILENO);
        close(fd);
    }
    else if (redir->type == REDIR_APPEND)
    {
        fd = open(redir->file, O_WRONLY | O_CREAT | O_APPEND, 0644);
        if (fd == -1)
            return (print_open_error(redir->file));
        dup2(fd, STDOUT_FILENO);
        close(fd);
    }
    else if (redir->type == REDIR_HEREDOC)
    {
        return (setup_heredoc(redir->file));
    }

    return (0);
}

4.6 ヒアドキュメント

ヒアドキュメントの概念

ヒアドキュメント(Here Document)は、シェルスクリプトで複数行のテキストを標準入力として渡す機能である。

cat << EOF
Line 1
Line 2
EOF

POSIX規格では、ヒアドキュメントのデリミタがクォートされていない場合、内容の変数展開が行われる。

ヒアドキュメントの実装

/**
 * ヒアドキュメントを設定する
 * @param delimiter デリミタ文字列
 * @return 成功時0、失敗時-1
 */
int setup_heredoc(const char *delimiter)
{
    int     pipefd[2];
    char    *line;

    if (pipe(pipefd) == -1)
    {
        perror("minishell: pipe");
        return (-1);
    }

    /* ヒアドキュメントの内容を読み取る */
    while (1)
    {
        line = readline("> ");
        if (!line)
        {
            /* Ctrl+D で終了 */
            ft_putendl_fd(
                "minishell: warning: here-document delimited by end-of-file",
                STDERR_FILENO);
            break;
        }

        if (ft_strcmp(line, delimiter) == 0)
        {
            free(line);
            break;
        }

        /* パイプに書き込む */
        ft_putendl_fd(line, pipefd[1]);
        free(line);
    }

    close(pipefd[1]);
    dup2(pipefd[0], STDIN_FILENO);
    close(pipefd[0]);

    return (0);
}

ヒアドキュメントのシグナル処理

/**
 * ヒアドキュメント読み取り中のシグナルハンドラ
 */
void    heredoc_sigint_handler(int sig)
{
    (void)sig;

    /* 改行を出力してプロンプトに戻る */
    write(STDOUT_FILENO, "\n", 1);
    g_shell.heredoc_interrupted = 1;

    /* readline を中断 */
    close(STDIN_FILENO);
}

/**
 * ヒアドキュメントを安全に読み取る
 */
int safe_heredoc(const char *delimiter, t_shell *shell)
{
    struct sigaction    sa;
    struct sigaction    old_sa;
    int                 result;

    /* ヒアドキュメント用シグナルハンドラを設定 */
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sa.sa_handler = heredoc_sigint_handler;
    sigaction(SIGINT, &sa, &old_sa);

    shell->heredoc_interrupted = 0;

    result = setup_heredoc(delimiter);

    /* シグナルハンドラを復元 */
    sigaction(SIGINT, &old_sa, NULL);

    if (shell->heredoc_interrupted)
        return (-1);

    return (result);
}

4.7 シグナル処理

シェルにおけるシグナル

シェルは複数のモードでシグナルを異なる方法で処理する:

| モード | SIGINT (Ctrl+C) | SIGQUIT (Ctrl+\) | |--------|-----------------|------------------| | インタラクティブ | 新しいプロンプト | 無視 | | コマンド実行中 | 子に転送 | 子に転送 | | ヒアドキュメント | 読み取り中断 | 無視 |

インタラクティブモードのシグナル

/**
 * インタラクティブモードのシグナルを設定する
 */
void    setup_interactive_signals(void)
{
    struct sigaction    sa;

    /* SIGINT: Ctrl+C */
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sa.sa_handler = interactive_sigint_handler;
    sigaction(SIGINT, &sa, NULL);

    /* SIGQUIT: Ctrl+\ を無視 */
    signal(SIGQUIT, SIG_IGN);
}

/**
 * インタラクティブモードのSIGINTハンドラ
 */
void    interactive_sigint_handler(int sig)
{
    (void)sig;

    write(STDOUT_FILENO, "\n", 1);
    rl_on_new_line();
    rl_replace_line("", 0);
    rl_redisplay();
}

コマンド実行中のシグナル

/**
 * コマンド実行中のシグナルを設定する
 */
void    setup_execution_signals(void)
{
    /* 親プロセスではシグナルを無視 */
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
}

/**
 * 子プロセスのシグナルを設定する
 */
void    setup_child_signals(void)
{
    /* デフォルトの動作に戻す */
    signal(SIGINT, SIG_DFL);
    signal(SIGQUIT, SIG_DFL);
}

4.8 終了ステータス

終了ステータスの規約

POSIXは終了ステータスの範囲を0-255と定義:

| ステータス | 意味 | |-----------|------| | 0 | 成功 | | 1 | 一般的なエラー | | 2 | シェルコマンドの誤用 | | 126 | コマンドは見つかったが実行不可 | | 127 | コマンドが見つからない | | 128+n | シグナルnで終了 | | 130 | Ctrl+C (SIGINT = 2) | | 131 | Ctrl+\ (SIGQUIT = 3) |

終了ステータスの管理

/**
 * 子プロセスの終了ステータスを取得する
 */
int get_exit_status(int wait_status)
{
    if (WIFEXITED(wait_status))
        return (WEXITSTATUS(wait_status));
    else if (WIFSIGNALED(wait_status))
    {
        int sig = WTERMSIG(wait_status);

        /* SIGINTの場合、改行を出力 */
        if (sig == SIGINT)
            write(STDOUT_FILENO, "\n", 1);
        /* SIGQUITの場合、"Quit" を出力 */
        else if (sig == SIGQUIT)
            ft_putendl_fd("Quit (core dumped)", STDERR_FILENO);

        return (128 + sig);
    }

    return (1);
}

/**
 * $? 変数を更新する
 */
void    update_exit_status(t_shell *shell, int status)
{
    shell->last_status = status;
}

4.9 ビルトインコマンド

ビルトインの必要性

一部のコマンドは、シェル自身のプロセスで実行する必要がある:

  • cd: 作業ディレクトリを変更
  • export: 環境変数を設定
  • unset: 環境変数を削除
  • exit: シェルを終了
  • 子プロセスでこれらを実行しても、親シェルには影響しない。

    ビルトイン判定

    /**
     * コマンドがビルトインかどうかを判定する
     */
    int is_builtin(const char *cmd)
    {
        static const char *builtins[] = {
            "echo", "cd", "pwd", "export", "unset", "env", "exit", NULL
        };
        int i;
    
        if (!cmd)
            return (0);
    
        i = 0;
        while (builtins[i])
        {
            if (ft_strcmp(cmd, builtins[i]) == 0)
                return (1);
            i++;
        }
        return (0);
    }
    

    ビルトインの実行

    /**
     * ビルトインコマンドを実行する
     */
    int execute_builtin(t_ast *ast, t_shell *shell)
    {
        const char  *cmd;
        int         saved_stdin;
        int         saved_stdout;
        int         status;
    
        cmd = ast->argv[0];
    
        /* 標準入出力をバックアップ */
        saved_stdin = dup(STDIN_FILENO);
        saved_stdout = dup(STDOUT_FILENO);
    
        /* リダイレクトを設定 */
        if (setup_redirections(ast->redirects) != 0)
        {
            restore_stdio(saved_stdin, saved_stdout);
            return (1);
        }
    
        /* ビルトインを実行 */
        if (ft_strcmp(cmd, "echo") == 0)
            status = builtin_echo(ast->argv);
        else if (ft_strcmp(cmd, "cd") == 0)
            status = builtin_cd(ast->argv, shell);
        else if (ft_strcmp(cmd, "pwd") == 0)
            status = builtin_pwd();
        else if (ft_strcmp(cmd, "export") == 0)
            status = builtin_export(ast->argv, shell);
        else if (ft_strcmp(cmd, "unset") == 0)
            status = builtin_unset(ast->argv, shell);
        else if (ft_strcmp(cmd, "env") == 0)
            status = builtin_env(shell->env);
        else if (ft_strcmp(cmd, "exit") == 0)
            status = builtin_exit(ast->argv, shell);
        else
            status = 1;
    
        /* 標準入出力を復元 */
        restore_stdio(saved_stdin, saved_stdout);
    
        return (status);
    }
    
    /**
     * 標準入出力を復元する
     */
    void    restore_stdio(int saved_stdin, int saved_stdout)
    {
        dup2(saved_stdin, STDIN_FILENO);
        dup2(saved_stdout, STDOUT_FILENO);
        close(saved_stdin);
        close(saved_stdout);
    }
    

    4.10 環境変数の管理

    環境変数配列の生成

    /**
     * 環境変数リストをexecve用の配列に変換する
     */
    char    **env_to_array(t_env *env)
    {
        char    **envp;
        t_env   *current;
        int     count;
        int     i;
    
        /* エントリ数をカウント */
        count = 0;
        current = env;
        while (current)
        {
            count++;
            current = current->next;
        }
    
        /* 配列を割り当て */
        envp = malloc(sizeof(char *) * (count + 1));
        if (!envp)
            return (NULL);
    
        /* "KEY=VALUE" 形式で格納 */
        i = 0;
        current = env;
        while (current)
        {
            envp[i] = create_env_string(current->key, current->value);
            if (!envp[i])
            {
                free_array(envp);
                return (NULL);
            }
            i++;
            current = current->next;
        }
        envp[i] = NULL;
    
        return (envp);
    }
    
    /**
     * "KEY=VALUE" 文字列を作成する
     */
    char    *create_env_string(const char *key, const char *value)
    {
        char    *result;
        size_t  key_len;
        size_t  value_len;
    
        key_len = ft_strlen(key);
        value_len = value ? ft_strlen(value) : 0;
    
        result = malloc(key_len + 1 + value_len + 1);
        if (!result)
            return (NULL);
    
        ft_memcpy(result, key, key_len);
        result[key_len] = '=';
        if (value)
            ft_memcpy(result + key_len + 1, value, value_len);
        result[key_len + 1 + value_len] = '\0';
    
        return (result);
    }
    

    4.11 エラー処理

    エラーメッセージの形式

    Bashに準じたエラーメッセージ形式:

    minishell: <コマンド>: <エラーメッセージ>
    

    エラー処理関数

    /**
     * エラーメッセージを出力する
     */
    void    print_error(const char *cmd, const char *msg)
    {
        ft_putstr_fd("minishell: ", STDERR_FILENO);
        if (cmd)
        {
            ft_putstr_fd(cmd, STDERR_FILENO);
            ft_putstr_fd(": ", STDERR_FILENO);
        }
        ft_putendl_fd(msg, STDERR_FILENO);
    }
    
    /**
     * open()失敗時のエラー処理
     */
    int print_open_error(const char *file)
    {
        print_error(file, strerror(errno));
        return (-1);
    }
    
    /**
     * コマンドが見つからない場合のエラー
     */
    void    print_command_not_found(const char *cmd)
    {
        print_error(cmd, "command not found");
    }
    

    4.12 実行エンジンの統合

    メイン実行関数

    /**
     * ASTを実行する
     * @param ast 抽象構文木
     * @param shell シェル状態
     * @return 終了ステータス
     */
    int execute(t_ast *ast, t_shell *shell)
    {
        int status;
    
        if (!ast)
            return (0);
    
        if (ast->type == NODE_PIPE)
            status = execute_pipeline(ast, shell);
        else if (ast->type == NODE_COMMAND)
            status = execute_command(ast, shell);
        else
            status = 1;
    
        shell->last_status = status;
        return (status);
    }
    

    4.13 まとめ

    本章では、プロセス実行モデルとコマンド実行を学んだ:

  • プロセスモデル: fork-exec、コピーオンライト
  • システムコール: fork、execve、wait/waitpid
  • ファイルディスクリプタ: dup/dup2によるリダイレクト
  • PATH検索: コマンドの実行ファイル検索
  • ヒアドキュメント: 複数行入力の処理
  • シグナル処理: インタラクティブモードとコマンド実行
  • 終了ステータス: POSIXの規約
  • ビルトイン: シェル内部で実行すべきコマンド
  • 次章では、パイプラインの実装について学ぶ。複数のコマンドをパイプで接続し、データをストリームとして処理する方法を解説する。

    ---

    参考文献

  • Ritchie, D. M. & Thompson, K. (1974). "The UNIX Time-Sharing System", Communications of the ACM, 17(7)
  • Bach, M. J. (1986). "The Design of the UNIX Operating System", Prentice Hall
  • Stevens, W. R. & Rago, S. A. (2013). "Advanced Programming in the UNIX Environment", 3rd Edition, Addison-Wesley
  • IEEE (2008). "IEEE Std 1003.1-2008: POSIX.1", IEEE Computer Society
  • Kerrisk, M. (2010). "The Linux Programming Interface", No Starch Press
  • Love, R. (2013). "Linux System Programming", 2nd Edition, O'Reilly