記述に誤りや見当違いな点がありましたら、ご指摘いただけますと幸いです。
はじめに
Windows で、複数行にわたる標準入力を受取りたいと思って書いた Ruby のコードが、予想外の動きをして困惑した話です。
次の環境で動かしました。
- Windows XP Professional + Ruby 2.0.0-p648(USB Rumix 2)
- Windows 8.1 Pro + Ruby 2.0.0-p648(USB Rumix 2)
- Windows 10 + Ruby 2.3.3-p222(RubyInstaller)
- Windows 10 + Ruby 2.4.1-p111(RubyInstaller2)
コンソール(とシェル?)の組合せは CKW-Mod + NYAOS です。
困ったこと
やろうとしたことは、コマンドラインから複数行の入力を受付け、直後にもう一度別の入力を受取るというもので、例えば次のようなコードです。
pry> p $stdin.readlines; p $stdin.gets
まず IO#readlines
で改行をまたぐ入力を取得し、続けざまに IO#gets
で一行入力を得ています。
実際に動かしている様子が次のアニメーションになります。
"fisrt line of #readlines"、 "second line of #readlines" の2行を入力後、 IO#readlines
への入力を確定するために EOF(Ctrl + Z)
を送っています。 すると Pry によって IO#readlines
の戻り値がエコー出力され、引き続き IO#gets
の入力待ちになります。ここで続けて "first line of #gets" という文字列を入力しようとしたところ、一文字目の "f" を押下した時点で IO#gets
は nil
を返し、このコードの実行が終了してしまいます。
つまり、 EOF
後の入力は取りこぼしてしまうようなのです。
この入力の取りこぼしは、冒頭で示した Windows 環境のいずれにおいても同じようにみられた一方で、 Ubuntu ではみられませんでした。
次のアニメーションは Ubuntu 16.04(Ruby 2.4.1-p111)で実行した様子です。
Ubuntu では IO#gets
は nil を返すことなく、改行を入れるまで入力を受付け思惑どおりに動いてくれます。 EOF
を送った後の入力をこぼすことなく、受取ってくれます。
解決方法がわからん
いろいろもがいてはみたものの、いずれも奏功せず悪あがきに終わりました。何か有効な対処法はないんでしょうかね…。自分でやってみたことは次のとおりです。
1. IO#raw
端末画面での入力処理モードには、次のようなものがあります。
-
cooked
入力はいったんバッファに溜められ、改行するまでプログラムに渡らない。また、Ctrl + C
やCtrl + D
といったコントロールコードをシェル側で解釈する。 -
cbreak
標準入力をバッファに溜めず、入力後即時でプログラムに渡す。コントロールコードは解釈される。 -
raw
標準入力をバッファに溜めず、かつコントロールコードも解釈せず通常の文字としてプログラムに渡す。
io/console
を require
すると IO#raw
が使えるようになります。
p $stdin.raw(&:readlines); p $stdin.gets
これを利用すれば EOF
も無効化できるのではと思いましたが、特に挙動に変化は見られませんでした。
2. IO#ungetc("\b")
EOF
が悪さをしているんだろうから、送ったらバックスペースで消せばいいという考え。
p $stdin.readlines; $stdin.ungetc("\b"); p $stdin.gets
バックスペースを送ったところで EOF
が消えるわけでないんだから、そりゃだめだわ…。
おわりに
これって Windows に依存する問題なんでしょうか。 Windows ではこのほかにも、 Pry や irb (正確には rb-readline
か) で日本語入力を正しくあつかえないという制限もあり、こと標準入力に関しては不自由があるな~と感じています。
私の環境では Windows は全部みられた現象なので、過去に困った方がいないのかと思ったのですが、ググった限りでは見つからず…。