シェルを自作してみた

はじめに

皆さん、シェルは何を使っていますか?
bash?、zsh?、fish?
シェルにはそれぞれ違いがあって、皆さん独自にカスタマイズして使っていると思いますが、「自作シェル」という方はあまりいないのではないでしょうか?
今回、自作シェルに挑戦してみたので、ブログとして残しておこうと思います。


なんで作ったのか

Twitterの一部界隈で、「どのシェルが良いのか」で話題になっていたので、「自作シェル」という選択肢を加えたら面白そうだと思い、自作できないか調べてみました。
調べたところ、「lsh」というシェルを見つけました。(局所性鋭敏型ハッシュじゃないよ!)
github.com
lshは300行くらいのCのコードで書かれていたので、「これくらいならできそう!」と思い、これをベースに作り始めました。

命名

コードを書く前に名前を決めました。
名前は「clash」です。
clashの意味を調べてみると、

ジャンジャンと鳴る、ガチャガチャ音がする、ガチャンと音を立ててぶつかる、ぶつかる、衝突する、(競技などで)(…と)ぶつかり合う、ぶつかり合う、(…と)衝突する、(…と)かち合う、(…と)釣り合わない

出典:clashの意味・使い方・読み方 | Weblio英和辞書

とあります。
既存のシェルとぶつかり合うくらいのシェルになると嬉しいですね。

実装

コードはこちらです。
github.com
2019/4/27時点で、以下のコマンドをビルトインコマンドとして実装しています。

  • echo
  • cd
  • pwd
  • mkdir
  • touch
  • ls
  • exit

CUI超初心者向けシェル」といったところでしょうか。
タブ補完もコマンド履歴もない、言ってしまえばとても不便なシェルです。
bashzshがいかに高機能か分かりますね!
仕組みですが、

  1. clash_loop()でループを回す
  2. clash_read_line()で入力を受け取る
  3. 入力された文字列をclash_split_line()でパースする
  4. clash_execute()でコマンド実行

という流れになっています。
lshとの違いとして、以下の変更を加えました。

  • プロンプトにカレントディレクトリ名を表示
  • pwd、ls、mkdir、touch、echoの各コマンドを追加

今回シェルを自作してみて、「なるほど」と思ったのは、cdコマンドの実装です。

int clash_cd(char **args) {
    if (args[1] == NULL) {
        fprintf(stderr, "clash: argument error");
    } else {
        if (chdir(args[1]) != 0) {
            perror("clash");
        }
    }

    return 1;
}

流れとしては、

  1. 入力文字列を受け取る
  2. 入力文字列に対応するシステムコールを実行するglibcの関数を呼び出す
  3. glibcの関数経由でシステムコールが実行される

と言った感じになると理解しました。
詳しい方からすると当たり前のことかもしれませんが、シェルの仕組み自体、全く知らなかった私にとっては、シェルの仕組みを理解する良いきっかけになりました。

おわりに

まだまだ機能は足りませんが、ある程度動くものが出来たので嬉しいです。
もし良かったら、新しいコマンドを実装してプルリクを投げていただけると喜びます。