MinGW の mintty で対話モード、ついでに vim 設定

English version is not available.

MinGW 版 mintty の現在

MinGW に含まれる mintty は対話モードを必要とする Windows ネイティブ・アプリケーションをうまく扱えません。sqlite3 や Python、Node.js や Git などが、この影響を受けます。対話モードで起動するオプションを持っているものは、"-interactive" や "-i" オプションをつけて起動すれば利用できますが、オプションがないものは手がありません。

これは、mintty を使う人なら周知の事実です。見なかった振りをすることもできますが、sqlite3、Python、Node.js、Git が仕様どおり使えないのは困るので、対策をしました。

通常のインストールなら、コマンドラインで、man mintty すると、マニュアルページを読むことができます。それを見ていくと、「制限事項」の最初にこの問題についての説明があります(拙訳ですが、日本語にしました)。

制限事項
コンソール問題
Minttyは、Cygwinがデフォルトで使いますが、Windowsのコンソールウインドウを完全に置き換える、というものではありません。xtermやrxvtと同じように、minttyも、子プロセスとは疑似ターミナルデバイスを通して通信します。CygwinはWindowsのパイプを利用してこの疑似ターミナルデバイスをエミュレートします。これによって、minttyから起動したWindowsネイティブのコマンドラインプログラムは、コンソールデバイスではなくて、パイプを見ることになります。その結果、そうしたプログラムにおいて、対話的な入力ができなくなることがしばしばあります。また、低レベルのWin32コンソール関数を直接呼ぶことができません。それでも、ファイルとしてコンソールに接続するプログラムはうまく動作するはずです。

MinGW では、コンソールへの入出力に /dev/tty[0] だけでなく、/dev/conout と /dev/conin という疑似デバイスを使うことができます。Windows で、コンソールをファイルとして開いた場合に、ファイル名として使う"CONOUT$"、"CONIN$"に対応しているようです(msdn: CreateFileを参照)。これらは実際にデバイスがあるというわけではないのですが、下のように、"ls デバイス名"とすると、あたかもファイルが存在するかのように見ることができます。ワイルドカードは使えません。tty コマンドでは、/dev/tty0 が返ります。

$ ls -al /dev/tty
crw-rw-rw- 1 user Administrators 5, 0 Nov  6 17:14 /dev/tty
$ tty
/dev/tty0

端末関係をまとめると、下のようになります。/dev/console は存在しないようです。

疑似デバイス名 接続先 疑似デバイス名 接続先
/dev/tty 入出力コンソール /dev/tty[0-?] 入出力コンソール(有効なのはtty0だけ)
/dev/ptmx 開けない /dev/ttyS[0-?] シリアルポート(利用不可)
/dev/conin 入力コンソール /dev/conout 出力コンソール

これらは、当然ながら、素の bash.exe でなら、下のように読み書きができます。もちろん、python.exe や node.exe を対話モードで使うことができます。

> bash
bash-3.1 $ echo hello > /dev/conout
hello
bash-3.1 $ cat /dev/conin
hello  (← キーボードから入力)
hello
bash-3.1 $ echo hello > /dev/tty
hello
bash-3.1 $ cat /dev/tty
hello  (← キーボードから入力)
hello
^C
bash-3.1 $

ところが、mintty を使って、bash.exe を起動すると、下のようになります。

> mintty -V
mintty 1.1.3
(C) 2013 Andy Koppe
License GPLv3+: GNU GPL version 3 or later
There is no warranty, to the extent permitted by law.

> mintty -e bash --login
$ echo hello > /dev/conout
$ (←プロンプトだけが返る)
$ echo hello > /dev/tty
hello
$ cat /dev/conin
cat: /dev/conin: Bad file number
$ cat /dev/tty
hello (←キーボードから入力)
hello
^C
$

/dev/conin、/dev/conout を使ったコンソールとの通信ができなくなるのですね。もし、Windows のファイルディスクリプタ CONOUT$ と CONIN$ がここにつながるとすると、mintty には制御できないということになりそうです。また、パイプラインについては、下で実験していますので、参照してください。アプリケーション側が、入出力がパイプだと判断すると、対話モードで起動しなくなる原因になります。

winpty というラッパ

さて、この現象の詳細ですが、Issue 56: Improve support for native console programs での議論がとても参考になります。作者 Andy Koppe さんの、

"python -i" があるんだから、それを使えばいいじゃん。

という意見はまったくそのとおりです。できないことはやらない、というソフトウェアの基本ですね。ところが、Andy Koppe さん、数か月後には、conin.exe というラッパを作って公開しています(#21の投稿)。この conin.exe は、いくつかのサイトで紹介されています。これでラップすると、Win32 アプリケーションでも対話モードで使えるようになるそうです。しかし、これは大量の Cygwin の dlls を必要とするので、MinGW 環境では使えません。それに、たぶんもうメンテナンスされていません。

ところが、その後、この議論の中で、Ryan Pricahrd という方が、2012年4月に、こんなのをつくったけど、といって、winpty というラッパをアナウンスしました。ありがたいことに、Cygwin 版と、MinGW 版があります(ソースは一つです)。上の code.google.com での議論を読むと、Console2 と同じ手法、ということは、ConEmu と同じ手法、ということは、conin.exe と同手法を使っている、ということになるようです。もうこの方法しかない、というように議論が収斂していく中で作られた様子がよくわかります。

この winpty を使うと、ほぼ望むとおりの振る舞いをしてくれます。Console2 のように表示が乱れるということもありません。

リンク先の GitHub からバイナリをダウンロードして、実行ファイルとライブラリをパスの通ったところに置けばインストールは終了です。通常どおり mintty を起動して、Windows アプリケーションは console.exe でラップするという使い方をするか、起動時に bash.exe そのものをラップしてしまうか、どちらかの使い方をします。

通常起動の場合、

$ /use/local/bin/console.exe python.exe

というようにしますが、フルパスで指定する必要はありません。タイピングの量が増えるので、.bashrc でエイリアス指定した方がいいでしょう。こんな感じです。

bash.exe をラップしてしまう場合は、エイリアスは必要なくて、mintty の起動スクリプトで、

というように指定します。

mintty + bash と、console.exe をかませたものとを比較した結果が、Issue 56: Improve support for native console programsの#67で投稿されています。console.exe を使った方が若干遅くなっているようです。同じテストをしてみると、私の環境では、下のようになりました。10万回 test を出力するテストです。

mintty.exe /bin/bash –login で起動した場合。

real   1m56.810s
user   0m2.917s
sys    0m0.733s

mintty.exe /usr/local/bin/console.exe /bin/bash –login で起動した場合。

real   0m13.515s
user   0m2.060s
sys    0m1.045s

投稿の結果とは逆です。下の場合が速いというよりも、mintty + bash の表示にかかる時間が長すぎます(原因は調べてませんが)。user の CPU 時間は 2.0s を切ることはないようです。だいたいこんなもの、と思ってください。ちなみに、cmd.exe から bash だけを起動して試すと、下のような結果でした。

real   0m17.612s
user   0m1.872s
sys    0m0.874s

表示ではなくて、計算するだけのテストもやってみました。Learning Python 第4版の p.509-511 にある例を使います。abs()ではなくて、加算の方を使いました。本をお持ちでない方のために説明しておくと、x + 10 という加算を、python の for ループ、リスト内包表記、map 関数、ジェネレータ式、ジェネレータ関数を使って、それぞれの時間を計測するというものです。画面表示があまり影響せず、計算の速度を比較するものと思ってください。

Windows の cmd.exe を使った場合。

> py -3 timeseqs.py
3.3.2 (v3.3.2:d047928ae3f6, May 16 2013, 00:06:53) [MSC v.1600 64 bit (AMD64)]
---------------------------------
forLoop  : 2.72152 => [10...10009]
---------------------------------
listComp : 1.63735 => [10...10009]
---------------------------------
mapCall  : 3.04892 => [10...10009]
---------------------------------
genExpr  : 2.10303 => [10...10009]
---------------------------------
genFunc  : 2.11403 => [10...10009]

mintty.exe /bin/bash –login の場合。

$ py -3 timeseqs.py
3.3.2 (v3.3.2:d047928ae3f6, May 16 2013, 00:06:53) [MSC v.1600 64 bit (AMD64)]
---------------------------------
forLoop  : 2.75238 => [10...10009]
---------------------------------
listComp : 1.75466 => [10...10009]
---------------------------------
mapCall  : 3.22996 => [10...10009]
---------------------------------
genExpr  : 2.29178 => [10...10009]
---------------------------------
genFunc  : 2.23834 => [10...10009]

mintty.exe /usr/local/bin/console.exe /bin/bash –login の場合。

$ py -3 timeseqs.py
3.3.2 (v3.3.2:d047928ae3f6, May 16 2013, 00:06:53) [MSC v.1600 64 bit (AMD64)]
---------------------------------
forLoop  : 2.83609 => [10...10009]
---------------------------------
listComp : 1.76609 => [10...10009]
---------------------------------
mapCall  : 3.37456 => [10...10009]
---------------------------------
genExpr  : 2.21019 => [10...10009]
---------------------------------
genFunc  : 2.23722 => [10...10009]

ちょっと微妙な感じですね。もう少し負荷をかけた処理をさせると、違いがはっきりするのかもしれませんが、大きな差は出ないようです。

不具合

  1. bash をラップして使うと、ssh でリモートのサーバに接続したときに、文字化けします。mintty の言語設定、サーバ側の言語設定がどちらも UTF-8 となっていても文字化けします。
  2. less を使うときに、方向キーがきかず、[jk]とスペースキーでしか行送りができなくなります。
  3. 子プロセスが異常終了したときに、ゾンビになることがあります。
  4. 子プロセスが異常終了したときに、まきこまれて winpty が死ぬときがあります。
  5. JavaScript、例えば、Node.js の uglifyjs などを、"console.exe uglifyjs" としては使えません。bash をラップしたコマンドラインでは使えます。
  6. 試していませんが、irb が対話モードにならないという情報もあります。
  7. vim を使うときに、日本語入力や外部コマンドの出力との関連で、何かを犠牲にしなければならないという状況になります。色づけとの関連では、termcap をうまく扱えていないのかもしれません。(おまけ参照)

mintty から起動した Windows アプリケーションのコンソール出力

code.google の議論では、Windows アプリケーションが WriteConsole 関数を使うのが原因では、という人もいたので、msdnの、WriteConsoleを参考に、試してみました。コンソールを開いて、"Hello, World"を書きだす C コードです。

Visual Studio 2012 Express に付属の cl.exe と、MinGW の gcc でコンパイルしてみました(どちらも結果は同じです)。

mintty bash –login で起動後、実行します。

$ ./conTest.exe
$

プロンプトが返るだけです。試しに、GetStdHandle() の引数を STD_ERROR_HANDLE に変えても同じでした。次に、mintty console bash –login で起動して、実行すると。

$ ./conTest.exe
Hello, World!
$

たしかに、console.exe があると、出力を捕捉してくれるようになります。このとき、conTest.exe が開いているコンソールは、mintty が裏で開いているデバイスにつながっていないのですね。

マニュアルページにあったように、コンソールをファイルとして開いた場合はどうなるか、確認してみましょう。

実行してみましょう。mintty bash –login の場合。

$ ./conFile.exe
$

同じです。mintty console bash –login の場合。

$ ./conFile.exe
Hello, World!
$

WriteConsole()、WriteFile() ともに失敗します。CreateFile() 関数に指定できるコンソールハンドルは、CONOUT$ と CONIN$ だけで、エラー出力へのハンドルは取得できないようです。マニュアルページで触れられていた「ファイル」というのは、これとは違う意味なのでしょうか?

それでは、通常の stdout、stdin はどうでしょうか。prinf() ではなくて、明示的に stdout、stdin を指定してみます。

これを実行すると、どちらも

$ ./output.exe
a
$

と出力されます。入力も試してみましょう(懐かしい K & R のコードです)。

これはちょっと変わります。mintty bash –login で実行させると次のようになります。

$ ./io.exe
a (←キーボードから入力)
b (←キーボードから入力)
x (←キーボードから入力)
a
b
$

おっと、入力がバッファにたまって出てきません。このバッファリングは、putc() の後に、fflush(stdout) すると、ちゃんと出力されますので、コンソール問題とは別です。mintty console bash –login で実行すると、こうなります。

$ ./io.exe
a (←キーボードから入力)
a
b (←キーボードから入力)
b
x (←キーボードから入力)
$

期待どおりの出力です。ここまでで、mintty が出力をコントロールできないのは、Windows アプリケーションがコンソールデバイスに出力を書き込んだときだというところまではわかりました。ちょっと覗いたところでは、mintty が子プロセスを fork() したときに、Windows のコンソールを開いているようですが、細かいところまではわかりません。それに、fork() の機構がないはずの MinGW でどうやってコンパイルするのかもわかりません。

それでは、「入出力がパイプラインになっている」というのを調べてみます。

cl でも gcc でもコンパイルできるはずですので、試してみてください。

mintty + bash です。

$ ./crt_isatty.exe
stdout has been redirected to a file
stdin has been redirected to a file
stdout is a pipe
stdin is a pipe

winpty + mintty + bash です。

$ ./crt_isatty.exe
stdout has not been redirected to a file
stdin has not been redirected to a file
stdout is a terminal
stdin is a terminal

Windows の cmd.exe です。

> crt_isatty.exe
stdout has not been redirected to a file
stdin has not been redirected to a file
stdout is a terminal
stdin is a terminal

素の mintty は、入出力がリダイレクトされて、パイプになっている、と返すのですね。これをチェックするプログラムなら、対話モードにならないでしょう。これを winpty は違う応答を返すようにしているのですね。isatty() または _isatty() でチェックする Python のようなアプリケーションは、これで対話モードで起動するようになるわけです。

おまけ: MinGW 付属の vim 設定

MinGW に付属の /usr/bin/vim.exe は、単独で配布されている Windows 版 vim とも、その日本語版とも違っているので、両者を併用するときには、.vimrc の中で場合分けが必要です。また、winpty を使う場合にも、少し挙動が変わるようです。

Windows 版の vim は、Windows アプリケーションなので、それ用にちゃんとチューニングされているようですが、コンソール用の vim.exe でも、さすがに mintty で使うことは想定していません。また、MinGW のそれは、あくまで MinGW アプリケーションなので、termcap を使っているなど、Unix 寄りになっていますが、POSIX 準拠の Cygwin ほどは Unix になっていないようです。

下の設定は、GUI ではなくて、mintty の中で起動して、日本語の表示と編集ができて、キーワードの色付けができるるところまでの設定です。winpty を使った場合に、TERM=xterm-color を指定してあるのは、.vimrc の中で場合分けができるようにするだけでなくて、これがないと日本語が表示できず、IME からの入力もうまくいかないので、必須の設定です。msys-vim の設定だけで、Windows 用 gvim の設定は入っていません。

.inputrc

/etc/inputrc.default を ~/.inputrc にコピーして、次のようにしてあります。

.bashrc

ロケール、文字コード設定。

console.exe で bash.exe をラップしている場合。

コマンド毎に console.exe を設定している場合。どちらも console.exe は使いません(*1)

  1. [2013-11-28 追記] Windows 版 の vim.exe には、console.exe を使わないと、画面描画ができませんでした。また、vim –help の日本語がうまく表示できません。ここの部分は下のように訂正します。 または、下のように、シェルスクリプトにするという手があります。WITHOUT_CONSOLE は、winpty を使わない mintty の起動スクリプトで環境変数を設定してあります。

.vimrc

実際は、Windows 版 vim との併用になるので、バージョン番号で場合分けをしたりしていますが、MinGW 付属の vim に関係するところだけをあげます。その他の設定は通常の vim と共通です。

色の設定は、Using vim color schemes with Puttyを参考にしましたが、256 色や 16 色では、表示が乱れるので、8としてあります(と、書きましたが、16 色でいけました: 2012-11-30追記)。winpty ありの方のキーマップ指定は、これがないと、カーソルキーが動かなくなり、vi 互換のような振る舞いをしてしまいます。

設定一覧

vim 項目 winptyあり winptyなし
msys-vim .minttyrc Locale=ja_JP
Charset=UTF-8
Locale=ja_JP
Charset=UTF-8
.bashrc LANG=ja_JP.UTF-8
OUTPUT_CHARSET=UTF-8
LANG=ja_JP.UTF-8
OUTPUT_CHARSET=UTF-8
.vimrc encoding=cp932
termencoding=cp932
encoding=cp932
termencoding=utf-8
日本語IME 使える 使える
:!ls の日本語 –show-control-charsが必要 できない(*1)
:!dir の日本語 できない できない
gvim .minttyrc Locale=ja_JP
Charset=UTF-8
Locale=ja_JP
Charset=UTF-8
.bashrc LANG=ja_JP.UTF-8
OUTPUT_CHARSET=UTF-8
LANG=ja_JP.UTF-8
OUTPUT_CHARSET=UTF-8
.vimrc encoding=cp932
termencoding=cp932
encoding=cp932
termencoding=cp932
日本語IME 使える 使える
:!ls の日本語 –show-control-charsが必要 –show-control-charsが必要
:!dir の日本語 できる できる
  1. mintty の charset と vim の termencoding を SJIS にして、–show-control-chars すれば日本語が読めますが、表示が乱れます。何より、日本語入力ができなくなってしますのでお勧めしません。

ls の出力は、msys-vim も gvim も、MinGW の /bin/ls.exe を使っていて、/bin/sh.exe や vimrun.exe から実行するようなので、.bashrc で ls に –show-control-chars をつけてエイリアスしていても、ここでは使われません。また、gvim の dir は、cmd.exe 組み込みのものが使われるようです。

制限事項

無理なことをしているわけですから、完全に満足のいく結果を求めてはいけないのですが、制限事項というか、どうしても直せないところが残りました。

  1. CursorLine に色づけをしたとき、winpty なしの msys-vim 以外はカーソルの次の行までハイライトされてしまいます。CursorColumn は正常です。
  2. winpty ありで gui でない Windows 版 vim をオプションなしで起動すると、スプラッシュスクリーン(というのでしょうか?)が表示されません。kaoriya 版も同様です。

One thought on “MinGW の mintty で対話モード、ついでに vim 設定

  1. Pingback: Cygwin で pt, peco が使えない備忘録

Leave a Reply