mkshにてviモードを使用した際の問題点まとめ(Ver.R52時点)
mksh(MirBSD Korn Shell)は、MirOS BSDプロジェクトで開発されている、kshのフリーソフトウェア実装である。
軽量でありながら、ヒストリ、ブレース展開、パイプラインにおける各プロセスの終了ステータスの取得など、実用性の高い機能も兼ね備えている。
そのため、dashとは異なりインタプリタとしてだけでなく、対話シェルとしても有用なシェルである。
しかし、私がmkshを対話シェルでviモードとして利用したところ、一般的にはあまり知られていないと思われる問題点を、幾つか発見した。
そこで、同じ環境で利用する人への参考として、また個人的なメモとして、問題点を記事にまとめた。
環境
Arch Linux (x86_64, 4.5.3-1-ARCH)
mksh (MIRBSD KSH R52 2016/03/04)
USP Personal Tukubai (20160402, ketaコマンドのみを実行例の整形に使用)
問題点
mkshをviモードで使用した際の問題点は、次の2つである。
プロンプトがUTF8に完全対応していない
キーバインドを変更できない。
以下、それぞれ詳細に解説する。
プロンプトがUTF8に完全対応していない
mkshには、UTF8に対応するためのオプション"utf8-mode"が用意されており、公式ではマルチバイト文字に対応している。
しかし、この"utf8-mode"は、emacsもしくはgmacsモードのみしか対応していない。
そのため、viモードで利用した場合、mkshはプロンプト上でマルチバイト文字を適切に扱うことが出来ない。
以下、例を挙げる。
(emacsモードでの例)
$ set -o emacs -o utf8-mode # emacsモードおよびutf8対応モードの有効
$ set -o | grep -E '(emacs|vi|utf8-mode) ' | keta # 設定の確認
allexport off inherit-xtrace on noexec off pipefail off utf8-mode on
braceexpand on keyword off nohup on privileged off vi off
emacs on login on nolog off restricted off vi-esccomplete off
$ echo ほげほげ^H^H^H^H | tee test1 # "ほげほげ"に対して、<Backspace>を4回入力して実行
$ # 期待したとおり、改行のみが出力される
$ od -t cx1 test1 # 出力の詳細な内容
0000000 \n
0a
0000001
(viモードでの例)
$ set -o vi -o utf8-mode # viモードおよびutf8対応モードの有効
$ set -o | grep -E '(emacs|vi|utf8-mode) ' | keta # 設定の確認
allexport off inherit-xtrace on noexec off pipefail off utf8-mode on
braceexpand on keyword off nohup on privileged off vi on
emacs off login on nolog off restricted off vi-esccomplete off
$ echo ほげほげ^H^H^H^H | tee test2 # "ほげほげ"に対して、<Backspace>を4回入力して実行
ほげ
$ # 4バイト分しか削除されず、プロンプトも含めた出力が妙な事になってしまう...
$ od -t cx1 test2 # 出力の詳細な内容
0000000 343 201 273 343 201 222 343 201 \n
e3 81 bb e3 81 92 e3 81 0a
0000011
このように、mkshのviモードでは、プロンプト上でマルチバイト文字を適切に扱うことが出来ない。
キーバインドを変更できない
他のシェルと同様に、mkshには組込コマンドとしてbindが実装されており、キーバインドを自由に変更することが可能になっている。
しかし、このbindによるキーマップの定義は、emacsモードにのみ適用され、viモードでのキーマップの定義は不可能である。
この理由は、実装にある。
ソースコードを読むと、viモードのデフォルトのキーバインドは、ハードコーディングされている。
具体的には、edit.cのvi_insert()やvi_cmd()等に、"case CTRL('r'):"といった様に実装されている。
そのため、viモードでは、bindコマンドによってキーマップを再定義することが、不可能になってしまっている。
対策
上記2点の問題に対する対策は、残念ながら今のところは無い。
参考にしたもの
$ man mksh
"MirOS: mksh – the MirBSD Korn Shell" https://www.mirbsd.org/mksh.htm
"MirBSD/mksh: MirBSD Korn Shell Source Code Mirror - GitHub" https://github.com/MirBSD/mksh
考察
※以下、執筆者の推測になります。
今回、mkshのviモードにおける問題点をまとめた際に感じたこと。
mkshのviモードって、実はとりあえず実装しただけの機能じゃないのかな?
そう感じた理由は、次のとおり。
現在でも開発が続いているにも関わらず、未だにUTF8に完全対応していない
viモードのキーマップは、コード内にハードコーディングされている
シェルにおけるviモードは、POSIX(IEEE Std 1003.1, 2013 Edition)で定められている
シェルにおけるviモードは、emacsモードと比較してマイナーである(明確な根拠は無い…)
以下、補足説明。
1.現在でも開発が続いているにも関わらず、未だにUTF8に完全対応していない
本文で説明したとおり。このご時世なのにUTF8に完全対応していないのは、ちょっと不自然な気がする。
emacsモードは完全対応しているのに、viモードは対応していない点も、更に変な感じ。
2.viモードのキーマップは、コード内にハードコーディングされている
言わずもがな。コードの再利用性や、ソフトウェアの拡張性を考慮すれば、ハードコーディングはしないはず。
emacsモードではキーマップがきちんと定義されているので、何だか統一感が無い。
更にemacsモードは、"emacsfn.h"でキーマップの再定義用のコードが書かれているのに、viモードでは該当するコードが存在しない点でも、何だか疎かにされているような気が。
3.シェルにおけるviモードは、POSIX(IEEE Std 1003.1, 2013 Edition)で定められている
これは、Twitterにて @bsdhackさんに教えていただいた情報。
@gin_135 POSIX:2013 (IEEE Std 1003.1-2013) には set -o vi が明記されていますね
— Mitzyuki IMAIZUMI (@bsdhack) 2016年5月12日
@gin_135 こちらをご覧下さい → https://t.co/8jQnztWb11
— Mitzyuki IMAIZUMI (@bsdhack) 2016年5月12日
mkshは、POSIX互換を謳っている。ということは、POSIX互換を謳うためには、POSIXで定められているviモードを実装する必要がある。
このことが、次の理由に関係してくると思った。
4.シェルにおけるviモードは、emacsモードと比較してマイナーである(明確な根拠は無い…)
これはあくまで僕の主観。シェルにおけるviモードは、emacsモードと比較して、使用人口が少ないと思う。
根拠としては弱いけれど、どのシェルでもデフォルトのキーバインドは、emacsモードになっているので。
(これまた根拠としては弱いけれど… Googleで検索した結果も、emacsモードの方がヒット数が多いので。)
そして、利用者が少ないということは、実装する際に後回しにされやすいのでは…と推測した。
雑記
🐺。oO(誰も実装しないなら、僕がやるしかないのか…??)
→ …でも、シェルを改良するだけのC言語コーディング力が無いorz本文中にも書いたのだけれど、mkshって、軽量なのにブレース展開や$PIPESTATUSも使えるので、対話シェルとしてはバランスが取れていて優秀だと思う。
→ なのに僕の大好きなviモードには、上記のような問題点があるので、実際にはすごく使いづらい… とっても勿体無い……単に「軽量」と書いても分からないので、簡単なベンチマークで比較してみた。
→ 比較するシェルは、zsh、bash、mksh、dashの4つ。 → 測定方法は、dateをwhile構文で延々と実行し、その出力をuniq -cへ渡して、一秒あたりの実行回数を計測するだけ。
$ zsh -l
$ while :; do date; done | uniq -c
78 2016年 5月 14日 土曜日 00:04:17 JST
209 2016年 5月 14日 土曜日 00:04:18 JST
210 2016年 5月 14日 土曜日 00:04:19 JST
211 2016年 5月 14日 土曜日 00:04:20 JST
208 2016年 5月 14日 土曜日 00:04:21 JST
213 2016年 5月 14日 土曜日 00:04:22 JST
^C
$ bash -l
$ while :; do date; done | uniq -c
250 2016年 5月 14日 土曜日 00:05:59 JST
617 2016年 5月 14日 土曜日 00:06:00 JST
600 2016年 5月 14日 土曜日 00:06:01 JST
518 2016年 5月 14日 土曜日 00:06:02 JST
452 2016年 5月 14日 土曜日 00:06:03 JST
482 2016年 5月 14日 土曜日 00:06:04 JST
^C
$ mksh -l
$ while :; do date; done | uniq -c
446 2016年 5月 14日 土曜日 00:07:23 JST
747 2016年 5月 14日 土曜日 00:07:24 JST
744 2016年 5月 14日 土曜日 00:07:25 JST
731 2016年 5月 14日 土曜日 00:07:26 JST
740 2016年 5月 14日 土曜日 00:07:27 JST
740 2016年 5月 14日 土曜日 00:07:28 JST
^C
$ dash -l
$ while :; do date; done | uniq -c
161 2016年 5月 14日 土曜日 00:07:49 JST
821 2016年 5月 14日 土曜日 00:07:50 JST
843 2016年 5月 14日 土曜日 00:07:51 JST
844 2016年 5月 14日 土曜日 00:07:52 JST
842 2016年 5月 14日 土曜日 00:07:53 JST
831 2016年 5月 14日 土曜日 00:07:54 JST
^C
こうやって比較すると、mkshはそこそこ高機能なのにdashに匹敵するほど速くて、実はかなり優良なシェルだと思う。
→ と思うからこそ、viモードに問題点があるのは、ますます勿体無い……