Windows GitBash で Python・Node.js・Docker が上手く動かない場合は winpty を設定する

Windows GitBash にて、$ python$ node コマンドを叩いて、プロンプト上で簡単なコードを動かしてみたかったのだが、どうもプロンプトの応答が戻ってこない。

また、$ docker 関連のコマンドを使うと、以下のようなエラーメッセージが返ってきた。

$ docker exec -it my-container bash
the input device is not a TTY.
If you are using mintty, try prefixing the command with 'winpty'

調べてみると、GitBash では、一部の対話式プロンプトを伴うコマンドは winpty というコマンドを経由して実行してやらないと、上手くプロンプトが表示されないようだ。

目次

まずは解決法だけ

ということで、

$ winpty python
$ winpty node
$ winpty docker exec -it my-container bash

という風に叩けば、正常に動いた。

winpty を設定している /etc/profile.d/aliases.sh

でも、node なんかはこんなことしなくても以前は使えていた気がするんだけどな…?と思い、もう少し調べてみると、原因が分かった。

以前、GitSDK および GitBash の起動速度を高速化するため、/etc/profile をゴリゴリ書き換えたことがあった。

この中で、「特に呼び出す必要ねえだろ」と独断で省いてしまった /etc/profile.d/aliases.sh の中で、winpty を使うエイリアスを設定していたことが分かった。

case "$TERM" in
xterm*)
  # The following programs are known to require a Win32 Console
  # for interactive usage, therefore let's launch them through winpty
  # when run inside `mintty`.
  for name in node ipython php php5 psql python2.7
  do
    case "$(type -p "$name".exe 2>/dev/null)" in
    ''|/usr/bin/*) continue;;
    esac
    alias $name="winpty $name.exe"
  done
  ;;
esac

こんな感じのコードが見えるかと思う。

for name in の行を観ると、nodepython、PostgreSQL の psql などのコマンドが並んでおり、alias $name="winpty $name.exe" と、winpty を組み込んだエイリアスを定義しているのが分かるかと。

通常どおり /etc/profile.d/aliases.sh を読み込んでいる環境なら、このファイルを編集し、for name in の行の末尾に docker を追加してやれば、docker コマンドが正常に動くようになる。必要に応じて docker-composedocker-machine なども入れておこう。

このファイルは使わない場合であれば、以下のようなエイリアス定義を ~/.bashrc にでも入れておけば良い。やっていることは同じだ。

alias docker='winpty docker'

# Python や Node.js の場合
alias python='winpty python'
alias node='winpty node.exe'

自分の環境であればコレだけで事足りた。


以降は補足…

問題の直接的な解決はココまで。以降はココで使用した winpty に関する調査記録。


winpty って何?

ところで、ココで使っている winpty とは何なのか。どうしてコレが一部のコマンドだけ必要で、最初から組み込んでおいてくれないのかを調べた。

TTY・PTS・PTY とは

winpty を理解する前に、まず TTY (TeleTYpewriter)PTS (Pseudo Terminal Slave)PTY (Pseudo Terminal) を知っておく必要がありそうだ。

tty は、あるマシンの標準出力の接続先デバイスを示す。

例えば1つのサーバに2人が同時に ssh している状態で、w コマンドを叩くと、pts/0pts/1 といった TTY 列が見えるだろう。

この状態で SSH 接続しているそれぞれのユーザが tty コマンドを叩くと、自分の TTY の仮想ファイルが /dev/pts/0/dev/pts/1 というように返ってくる。

tty コマンドの結果が /dev/pts/0 なユーザのセッションにおいて、

$ echo 'TEST' > /dev/pts/0

と実行すると、自身のプロンプトに TEST という文字列が出力される。さらに、ココで別の TTY を指定すると、

$ echo 'TEST' > /dev/pts/1

今度は /dev/pts/1 のユーザのプロンプトに、突然 TEST の文字が出力される。

このように、TTY/dev/pts/ 配下の仮想ファイルで入出力を扱えるデバイスということになる。ココで登場する PTS は、仮想端末 (ターミナル) のスレーブ (接続先) ということ。接続先のサーバがマスターで、繋ぎに来ている2人のユーザのターミナルがスレーブという扱いになる。

ココまで来れば、PTY が「仮想端末」と呼ばれ、それが何を意味しているかも分かるだろう。今は「ターミナル」と呼ぶので「PTY は Pseudo Terminal の略」と表現するが、「じゃあその『Y』はどこから来たの?」というと、TTY にある「TYpewriter」の名残と同じで、「Pseudo (tele) TYpewriter」からの発展だと分かる。

pty stands for Pseudo-Terminal. Has anyone wondered, if tty is for Tele-Typewriter, why pty is not Pseudo-Typewriter?

GitBash のベースとなる mintty

GitBash のベースとなる mintty (MinTTY) は、ターミナルエミュレータと呼ばれるソフトになる。いわゆる「黒い画面」を作るインターフェースでしかなく、コマンド群は Cygwin や MSYS が提供するという関係だ。GitSDK や GitBash インストーラは、MSYS と mintty、そして git コマンドを統合した環境を構築・インストールしているワケである。

mintty のバグ?

というワケで、普段我々は、GitBash や GitSDK のフロントエンドとして、mintty というターミナルエミュレータを使っているのだが、この mintty のバグ (仕様?) により、一部のプロセスで対話モードのプロンプトが正常に表示できないようだ。

minttyはとても使い勝手が良い環境なのですが、「一部のアプリで対話モードが動作しない」といった問題点が存在します。

cygwinのterminalであるminttyっていうexeがネイティブのpython以外のpythonをたたくと固まるという既知のバグなんだそうです。

なので、mintty.exeの代わりにwinpty.exeというものを入れて、cygwinのterminalをラップしてやる必要がある。

本題・winpty は何なのか

winpty のリポジトリは以下にある。

ココを見ると、mintty と、そこから呼ばれた Windows 向けのプログラムとの間を仲介し、別プロセスとして立ち上がった winpty が出力を上手く処理してくれるようだ。

mintty の対話プロンプトに関する既知の不具合に対し、橋渡しを行う仲介プログラムというワケだ。

じゃあ全部のコマンドを winpty 経由で実行したら?

winpty を経由しないと、Python などのプログラムが動かないという問題は、いくつか Issues が挙げられている。

一番最初に挙げられた Issue はコレっぽい。

次に紹介する Issue の中で、「全部のプログラムを winpty 経由で実行させたらこんな問題起こらなくね?」と言われているが、次のような回答が付いている。

Why not just run everything through winpty?

@fletchowns measure it. No, seriously, measure the difference in startup time.

単純な話だが、全てのプロセスを winpty に中継されると、それだけプロセス数が増加し、処理時間が余計にかかってしまうから、一部の有名なコマンドだけ抜き出して設定しているようだ。

また、以下の記事では次のように書かれている。

Otherwise, and probably even with such notice, there's going to be these kind of issues popping in continuously :).

I agree. It is of no use pointing out that we try to support Git here, and not a bazillion of interactive consoles for other software.

/etc/aliases.sh でエイリアスを付与するやり方は問題を解決はできたが、今後似たような問題が起こるプログラムを見つけたらどんどん追記していかないといけなくなるよね? という問いに対し、「そうだけど、GitBash は Git をサポートするためのモノで、他のソフトを総合的にサポートするモノではない」という回答。

つまり、mintty 由来のバグの影響を受けるソフトがあるのは分かるが、Git For Windows はその全てに上手く対応するつもりはないよ、ということみたい。

そんなワケで、我々は GitBash を使う限りは、問題が起こるプログラムを見つけ次第、必要に応じて winpty を挟むしかないということになる。とりあえず納得。

参考文献

winpty を付与する件について

TTY・PTS・PTY

WinPTY