ゆるく理解する自作シェル実装 1 - シェルのイメージを掴む -
2024-10-15
azblob://2024/10/15/eyecatch/2024-10-15-shell-impl-01-000.jpg

はじめに

私が通っているスクールでシェルの再実装を行う機会があったので、その内容を忘れないようにまとめておこうと思います。

記事作成に当たって下記のような立ち位置で進めていこうと考えています。

シェルとその内部実装をかいつまんで大まかに理解することを目標にしています。
情報の集約・簡略化などによって正確な情報が欠如しているかもしれません。
あくまで、ゆるくシェルについてのイメージがわく程度の読み物としてみていただければと思います。

シェルとは

ユーザーに対して、カーネルを含む様々なソフトウェアへのインターフェースを提供するプログラムです。シェルの具体例としてbashやPowerShellなどが挙げられ、これらはコマンドラインインタープリターに該当します。
インターフェースとしてのシェル

コマンドラインインタープリターとは

コマンドラインインタープリターとは、ユーザーがキーボードから入力したテキスト形式のコマンドを読み取り、それを解釈してコンピューターが実行可能な命令に変換して実行するプログラムのことです。そして、これは読み取り-評価-出力の一覧の流れを繰り返すため、REPL(Read-Eval-Print Loop)に該当します。
REPL

シェルの振る舞い

これらを考慮して、シェルのプログラムの大まかな処理の流れをまとまると、任意の入力を受け取り、それを評価して結果を出力することを繰り返すプログラムであるということがわかります。
シーケンス図っぽく表すとこんな感じですね。
シーケンス1

このもやもやしている部分(特にEvalの部分)が難しいので、今回は簡単な処理に置き換えて、入力として受け取ったものを出力するだけだと考えてひとまず実装してみましょう。
シーケンス図っぽく表すとこんな感じですね。
シーケンス2

実装

Go言語で上記のシーケンス図にならって実装すると下記のようになります。

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    reader := bufio.NewReader(os.Stdin)

    for {
        fmt.Print("$ ")

        line, _, err := reader.ReadLine()
        if err != nil {
            log.Println("ReadLine Error: messsage = %v", err)
        }

        fmt.Println(string(line))
    }
}

まとめ

今回はシェルについてのおおまかなイメージの理解とコマンドラインインタープリターとして振る舞うための大枠の実装を行いました。
次回以降でシェルの基本機能を徐々に理解・実装をしていきたいと思います。