自作シェルの進捗(2025年3月29日その2)

Sat Mar 29 17:22:50 JST 2025 (modified: Sat Mar 29 22:00:17 JST 2025)
views: 170, keywords:自作シェル,rusty_bash,寿司シェル

 前の記事の続きです。前の記事のとおり、 Bashの公式リポジトリのテストを使い始めました。しかしこのテスト、 エッジケースが多い上にテストスクリプト自体が bash-completion並みに変態なので、 それに対応せねばならずいろいろ調べてました。

Bashではfunctionと書けば()が省略できる

 テストスクリプトを自作シェルに読み込ませると 「functionなんてコマンドねえよ」 (=functionというキーワードで始まると、 関数の定義部分が認識できない) というエラーが出たのでおかいしなあと色々調べてたんですが、 テストスクリプトの当該のところを見たら関数の定義に() がついてません。

function f1
   {
       typeset -n v=$1 #そういえばtypesetもまだ実装してない
   
       v=inside
   }

どうやらfunctionと明示的に書いて関数を定義すると ()が要らんようです。確かにシェルの関数の() はなんの意味もない飾りなので合理的ではあるけど、 そんなこと知るか。

 ということで自作シェルも()なしで大丈夫なように直しました。

### alphaブランチ ###
   🍣 function oniku { echo; } ; oniku
   

そんなこと知るか(重要でないけど2回言う)。

Bashのglobstarは同じパスを何回も出す

Bashでshopt -s globstarを実行すると、 ファイルグロブ(ワイルドカード)で** という表現が使えるようになります。 これはあるディレクトリの階層でecho ./**みたいに使うと、 それ以下にあるファイルやディレクトリのパスを すべて列挙できるという機能です。

 で、実装は辛そうなので後回しにしてたのですが、 Bashの公式テストに叱られるので実装しました。 ビビってた割には簡単に実装できたのですが、 うまく動いているはずなのになぜかテストが通りません。

 テストの正解を見たりBashの挙動を調べたりしていたところ、 Bashが同じパスを何回も出力しているのを発見しました。 再現するとこんな感じ。

### Bashです ###
   $ shopt -s globstar
   $ mkdir -p a/{a,b}/{a,b}
   $ cd a
   $ echo **/a/**
   a a/a a/a a/b b/a
   # ↑重複↑しとるやんけワレ

そんなこと知るか(3回目)。

 テストスクリプトにこう書いてあるので、

# same as ksh93
   s '**/a/**'
   p **/a/**
   
   
   # same as ksh93
   s '**/a/**/**'
   p **/a/**/**
   
   
   # same as ksh93
   s '**/a/**/**/**'
   p **/a/**/**/**

kshに互換性を持たせているか、 同じ実装になっているようです。 さすがにこれは真似しなくていいだろうということで、 テストの正解の方をuniqしてテストはパスしたということにしました。

IFSよくわからん

 単語の区切り文字を変えるための変数IFSへの対応が中途半端で、 テストで叱られまくったので自作シェルを改良しました。 ただ、いろいろ挙動が謎なのでテストはだいたいパスしたけど頭を抱えてます。 わけのわからん挙動、一例だけ挙げておきます。

### Bashの例です ###
   
   ### : で'A:A:::'を区切るので           ###
   ### 5個に分割されるが、変数が2つなので ###
   ### ふたつめのA以降はbにくっつく       ###
   $ IFS=":" ; read a b <<< 'A:A:::' ; echo "($a)($b)"
   (A)(A:::)
   ### 区切りがひとつ減るのでコロンも1つ減る ###
   $ IFS=":" ; read a b <<< 'A:A::' ; echo "($a)($b)"
   (A)(A::)
   ### 区切りがひとつ減るので・・・なんで2つ減るの??? ###
   $ IFS=":" ; read a b <<< 'A:A:' ; echo "($a)($b)"
   (A)(A)

これがBashの挙動なんだから従え言われても拒否したいのですが、 テストに通らんのでしぶしぶ従いました。

 もう一点困ってるのは、 端末上のBashの挙動がテストでの挙動と合わないことです。

### 自作シェルをテストにかける ###
   ueda@p1gen6:~/GIT/bash_for_sush_test/sush_test$ ./single_test run-ifs-posix
       Finished `release` profile [optimized] target(s) in 0.02s
   1,254c1
   < THIS IS BASH COMPATIBILITY TEST MODE
   < IFS=": "; x="  :"; set x $x; shift; echo "[$#]($1)($2)" # expected "[2]()()" got "[1]()"
   ・・・
   # ↑ 出力は"[2]()()"のはずなのに自作シェルの出力は"[1]()"だと叱られる。
   < # tests 3712 passed 3208 failed 504 -- fundamental IFS error -- 6856 tests expected
   
   ### Bashで検証してみる ###
   $ IFS=": "; x="  :"; set x $x; shift; echo "[$#]($1)($2)"
   [1]()()  #←自作シェルと同じ(()がひとつ多いのは無視で大丈夫です)
   
   ### Bashでテストスクリプトを実行 ###
   ueda@p1gen6:~/GIT/bash_for_sush_test/tests$ THIS_SH=bash bash ./ifs-posix.tests
   # tests 6856 passed 6856 failed 0
   # ↑全部成功と出る

何が原因なのか調査中と言いたいところですが、 他のことを優先すべきということで放置してます。 (と書いたけどテストが全部できてないのでなんかパースエラーがあるっぽいので調べます。) 自作シェルのほうは3712中504が失敗と出ていますが、 ほとんどは端末上のBashとは一致しているっぽいです。

その他

 テストスクリプトで使われていたのでヒアドキュメントを実装しました。 まだちょっと雑です。

 また、自作シェルはBashを一部拡張した部分があるんですが、 それが発動するとテストが通らなくなるので新たに-b (bash compatibility)というオプションをつけました。 上のログにTHIS IS BASH COMPATIBILITY TEST MODEと出ているのはそれです。 本家Bashにも各バージョンの挙動を再現するための --compat〇〇というオプションがあるんですが、 これもおいおい(数年後)サポートしようと思います。

 明日はprintfの実装が雑なのを修正し、 テストで使ったときにエラーが出ないようにする予定です。 その後、letを実装する予定です。

 自分で実装しているとほんとに分からんことだらけになります。 たぶんこれでもBashについてはよく理解しているほうだとは思うのですが、 偉そうなクソブロガーには永久になれそうもありません・・・

現場からは以上です。

ノート   このエントリーをはてなブックマークに追加 
 

prev:自作シェルの進捗(2025年3月29日) next:自作シェルの進捗(2025年3月29日その2)

やり散らかし一覧

記事いろいろ