🌟

minishellをどこから手を付けていいのか分からない人へ(pipex, Exam Rank 04)

2024/10/08に公開

by 課題のハードルを下げようの会

課題

bashの再実装

1時間で全体像を把握する

「bashを再実装しろ」と言われてもどこから手を付けていいのか分からない人が多いと思います。

susamiさんの書かれたこの記事は素晴らしいのですが、ざっくり全体像を掴むにはちょっと難しい。「よく分からないまま全部写経」みたいになっちゃいがち。
https://usatie.notion.site/minishell-29921d3ea13447ad897349acd5733d5e

まずはじめに全体像を把握するには、下記コードがオススメです。
このコードは『ふつうのLinuxプログラミング 第2版』 p.282 12.5練習問題(3)の解答です
https://github.com/aamine/stdlinux2-source/blob/master/sh2.c

とりあえず、コンパイルして動かしてみましょう。
readlineも使わずに、わずか330行でこんなことができます。

# コンパイル&実行
% cc sh2.c && ./a.out

$ mkdir testdir
$ cd testdir
$ pwd
/home/syagi/testdir
$ echo "hello, shell" | cat | cat -e > file.txt
$ ls
file.txt
$ cat file.txt
"hello, shell"$
$

多段パイプ、リダイレクト、ビルトインがシンプルに書かれています。
ぼくらのチームは最初にこのコードを大きい画面に映して2人で一緒に1,2時間かけてじっくり読みました。
pipex, Exam Rank 04もこのコードをちゃんと理解していたら、サクっとクリアできるはずです。

先ほどのコードをちょっと読んでみましょう

とりあえずmain関数を見てみましょう。
なんか、無限ループの中でプロンプトを表示して、コマンドが入力されるのを待つようです。

main(int argc, char *argv[])
{
    program_name = argv[0];
    for (;;) { // 無限ループ
        prompt(); // プロンプトを表示して、入力待ち
    }
    exit(0);
}

prompt()はこんな感じ

prompt(void)
{
    // 省略
    fprintf(stdout, "$ "); // プロンプトを表示して
    fflush(stdout);
    // 省略
    cmd = parse_command_line(buf); // 構文解析して
    // 省略
    if (cmd->argc > 0)
        invoke_commands(cmd); // コマンドを実行
}

invoke_commands()はこんな感じです。

invoke_commands(struct cmd *cmdhead)
{
    int st;
    // 標準入出力を退避
    int original_stdin = dup(0);
    int original_stdout = dup(1);

    exec_pipeline(cmdhead); // パイプを実行
    st = wait_pipeline(cmdhead); // パイプの実行結果待ち
    // 退避していた標準入出力を戻す
    close(0); dup2(original_stdin, 0); close(original_stdin);
    close(1); dup2(original_stdout, 1); close(original_stdout);

    return st;
}

なぜ標準入出力を退避するかというと、リダイレクトで標準入出力を書き換えてしまうため、後で元に戻すためです。

exec_pipelineの中身はpipexで実装しているはずなので省略します。

システムコールの使い方を把握

システムコールの使い方を一通り学ぶには『ふつうのLinuxプログラミング 第2版』がオススメです。
ネットの情報だけで42の課題を進めるのは結局遠回りなことが多いです。ネットで全体像を把握したら、初手として、まず本を読むのが結局は近道だと僕は考えています。
必要に応じて『linux プログラミングインターフェース』を参照しましょう。値段も高いし、分厚くてなんとなく敬遠しているのなら、一度パラパラ開いてみて下さい。minishellをやっている内に、なんとなく不安に感じていたことや、疑問点の答えが書かれていて、めちゃくちゃテンションが上がるはずです。

構文解析

上記コードでは、構文解析が貧弱なため、これを拡張してminishellに対応させるのは難しいです。

簡単な構文解析は『低レイヤを知りたい人のためのCコンパイラ作成入門』の「ステップ3:トークナイザを導入」あたりを読めば実装できるようになります。
https://www.sigbus.info/compilerbook#ステップ3トークナイザを導入
なおmandatoryだけであれば、木構造にしなくても、線形リストで実装できます。

仕様は正しく把握しよう(これが一番大事)

ここまで来れば自分たちで考えて全部書けるはずです。
後はbashの挙動を調べながら、コードを書くだけ・・・なのですが、仕様は正確に把握しましょう。間違った仕様でどれだけ頑張ったところで、それはただのゴミなのです。

manを読んだり、本家のコードを読むのが正攻法ですが、下記サイトから過去のレビューフィードバックを調べることができます。minishellにはみんな引っかかる落とし穴みたいものがいっぱいあるので注意して下さい。

https://42feedback.vercel.app/

大事なことなのでもう一度書きます。上記サイトから過去のレビューフィードバックを読むことができます。仕様を勘違いしていると、コードを大幅に書き換えることになり、手戻りが発生します。そうやって何度も書き直している内に開発期間がどんどん延びていきます。最初に正しい仕様をしっかり把握しておくことがメチャクチャ大事です。
(必要以上に厳しいフィードバックもあるので、自分達はどこまで作るのか決めて、ディフェンスできそうなら「実装しない」という選択肢もあります。)

いいですか、過去のフィードバックをよく読むのです。

Discussion