📘
「コマンドラインシェル??? 誰でも作れますよ」
「まさか、あなた…作れないんですか?」(不安を煽ってゆく商法)
縛り条件
- インタラクティブなシェルを作る
- 自分で作った nyagos 向けの既存パッケージは使わない
- 他人が作った既存パッケージは使ってよい(作成を依頼してはダメ)
- 自分のコードは go run コマンドで動かせる範囲のみ(つまり1ファイル)
- 動くこと!
必要最小限のミニマムな骨格のフレームのスケルトンなイメージの青写真
while ( true ){
text = readline()// 一行入力:最悪 gets でもよい
system(text) /// 一行実行:最悪 CMD.EXE とかに丸投げしてもよい
}
最初は「最悪」だけでいいんです
今回のポリシー的な方向性についての方針
- 一行入力は、"github.com/mattn/go-tty" についてた ttyutil の ReadLine を拝借しよう
- 最初はgets相当をやろうかと思ったが、bufio.NewScannerとか、かえってめんどいように思った
- が、実際はそんなことないくらい、ttyのインスタンス用意で行数消費した
- 一行実行くらいは "os/exec" で軽く実装しよう
カタカタカタ
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/mattn/go-tty"
"github.com/mattn/go-tty/ttyutil"
)
func mains() error {
fmt.Println("Tiny Shell. Type Ctrl-D to quit.")
tty1, err := tty.Open()
if err != nil {
return err
}
defer tty1.Close()
for {
clean, err := tty1.Raw()
if err != nil {
return err
}
fmt.Println("Ready")
text, err := ttyutil.ReadLine(tty1)
clean()
if err != nil {
return err
}
fields := strings.Fields(text)
if len(fields) <= 0 {
continue
}
cmd := exec.Command(fields[0], fields[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Run()
}
}
func main() {
if err := mains(); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}
説明しよう!
-
main()
の中からmains()error
を呼ぶのは、エラー処理を簡略化する、わたくしのスタイルなんで、あんまり気にしないでいいです。 - よく見ると、半分くらい https://github.com/zetamatta/go-readline-ny/#example2go の改変物だが、誰もコピペがダメとは言っちゃあいない(キタねぇぞ!)
- プログラムの上半分は一行入力を行う(=ReadLine を呼ぶ)ための、準備作業(1文字キー入力の初期化)に費やされている。ここは go-tty の仕様だからシヨウがない。。
- go-tty はそもそものはコンソール入力のライブラリで、これを使うと Enter キーなしのキー取得が可能になる。ttyutil.ReadLine はそのサンプルとして提供されているもの
- go-tty はUNIXの端末操作のラッパーを模している(Windowsではラッパーではなくエミュレーションしている)。UNIX系端末には Cocked モードと Raw モードがあり、前者は Enterが押下されるまで端末任せの編集が行われるが、この状態では Backspace など簡単な訂正が可能なだけで、まともな編集機能があるとはいいがたい。Emacs-like な複雑な編集を行うには Rawモードといって、入力文字をそのままアプリケーションに送信するモードに切り替えて、アプリケーションで細かくキーの解釈・カーソルの移動を行わなくてはいけない。今回はそういうめんどくさいことは ReadLine に丸投げだ!
- プロンプトだが、どうも ttyutil.ReadLine だと上書きされるのか、うまくインラインに書けなかったので、今回は F-BASIC風に「Ready」をプロンプトにして、8bit風味にしてごまかした。
- 後半は入力した一行を単語単位に分割して、それを exec.Command にくべているだけ
「ね、簡単でしょ?」
TO BE CONTINUED...?
- 環境変数は参照も変更もできません
- カレントディレクトリの変更もできません
- パイプラインもリダイレクトもできません(「甘ったれるな!」「逆ギレ?」)
- スクリプトもありません。Lua?なんですかそれは
なんとかせんといかんのでは?(気が向いたら、続きを書きましょう)
Discussion