RubyのNet::TelnetはPromptを適切に設定しないと接続がTimeoutする

# はじめに
telnetは、汎用的な双方向通信プロトコルであり、平文で通信する。以前[scpが失敗する件](https://qiita.com/montblanc18/items/b93fa4082e3bc2702a7f)について記事にしたが、.bashrc等の設定次第で、RubyでNet::Telnetを使用するとき、失敗することがある。

# Net::Telnetクラス
## 基本の使い方
[Rubyのマニュアル](
https://docs.ruby-lang.org/ja/2.2.0/class/Net=3a=3aTelnet.html)を確認すると、

```ruby
require 'net/telnet'

# リモートホスト "foobar" に接続
# タイムアウトは 10 秒
localhost = Net::Telnet.new("Host" => "localhost",
                            "Timeout" => 10)

# ログインし、プロンプトが出るまで待ち合わせる
telnet.login("your name", "your password") {|c| print c}

# ls コマンドを実行し、実行後、プロンプトが出るまで待ち合わせる
telnet.cmd("ls") {|c| print c}

# sleep で 5 秒
telnet.cmd("sleep 5 && echo foobar &") {|c| print c}

STDOUT.flush # <- これがないとここまで処理が来てることがわかりにくい

# 前のコマンドの出力を待ち合わせる
telnet.waitfor(/foobar\Z/) {|c| print c}

# ログインセッションの終了
telnet.cmd("exit") {|c| print c}
telnet.close
```

のように使用する。作成したtelnetインスタンスは、サーバから返ってきた平文を解釈するが、一番最後にプロンプト(例```user @ server $ ```)が表示されると、処理が終了したと認識し、次の処理に移れるようになる。

## 使用例
```irb
irb(main):001:0> require "net/telnet"
=> true
irb(main):002:0> pop = Net::Telnet.new("Host" => "192.168.179.9")
=> #<Net::Telnet:0x00007f8e159a59e8 @options={"Host"=>"192.168.179.9", "Port"=>23, "Prompt"=>/[$%#>] \z/n, "Timeout"=>10, "Waittime"=>0, "Binmode"=>false, "Telnetmode"=>true}, @telnet_option={"SGA"=>false, "BINARY"=>false}, @sock=#<TCPSocket:fd 9, AF_INET, 192.168.179.127, 62402>>
irb(main):003:0> pop.login("hikaru", "パスワード")
=> "\nKernel 3.10.0-957.1.3.el7.x86_64 on an x86_64\nClara login: hikaru\nPassword: \nLast login: Sun Jan 13 23:18:12 from ::ffff:192.168.179.127\n[hikaru@Clara ~]$ "
irb(main):004:0> pop.cmd("ls")
=> "ls\nDesktop    Downloads  Music\tPublic\t   Videos  hikaru\nDocuments  Dropbox    Pictures\tTemplates  cern    zabbix\n[hikaru@Clara ~]$ "
irb(main):005:0> exit
```

## プロンプトの解釈について
Net::Telnetクラスでプロンプトの解釈は、クラスをnewするときに```Prompt```引数で指定でき、デフォルトだと```"Prompt"=>/[$%#>] \z/n```となっている。プロンプトの書き換えは.bashrcなどで```PS1=hogehoge```の形で書き換えるが、ここで指定した形とNet::TelnetのPromptがアンマッチだと、処理が終了したと認識できず、Timeoutする。

再度Net::Telnetのデフォルトを確認すると、```"Prompt"=>/[$%#>] \z/n```のように、途中で半角スペースが必須となっている。そのため、サーバ側のPS1の書き換えで半角スペースを省いてしまうと、Net::TelnetがTimeoutする。

## Timeoutする実行例

```bash:.bashrc@server
PS1="<\u @ \t>" # 最後に半角スペースなし
```

```irb:@Client
irb(main):001:0> require "net/telnet"
=> true
irb(main):002:0> pop = Net::Telnet.new("Host" => "192.168.179.9")
=> #<Net::Telnet:0x00007f9af40ae060 @options={"Host"=>"192.168.179.9", "Port"=>23, "Prompt"=>/[$%#>] \z/n, "Timeout"=>10, "Waittime"=>0, "Binmode"=>false, "Telnetmode"=>true}, @telnet_option={"SGA"=>false, "BINARY"=>false}, @sock=#<TCPSocket:fd 9, AF_INET, 192.168.179.127, 62401>>
irb(main):003:0> pop.login("hikaru", "パスワード")
Traceback (most recent call last):
        5: from /Users/hikaru/.rbenv/versions/2.5.0/bin/irb:11:in `<main>'
        4: from (irb):3
        3: from /Users/hikaru/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/net-telnet-0.2.0/lib/net/telnet.rb:748:in `login'
        2: from /Users/hikaru/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/net-telnet-0.2.0/lib/net/telnet.rb:696:in `cmd'
        1: from /Users/hikaru/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/net-telnet-0.2.0/lib/net/telnet.rb:557:in `waitfor'
Net::ReadTimeout (timed out while waiting for more data)
```

## Timeoutしない例

```bash:.bashrc@server
PS1="<\u @ \t> " # 最後に半角スペースアリ
```

```irb:@client
irb(main):001:0> require "net/telnet"
=> true
irb(main):002:0> pop = Net::Telnet.new("Host" => "192.168.179.9")
=> #<Net::Telnet:0x00007fe15e1a8938 @options={"Host"=>"192.168.179.9", "Port"=>23, "Prompt"=>/[$%#>] \z/n, "Timeout"=>10, "Waittime"=>0, "Binmode"=>false, "Telnetmode"=>true}, @telnet_option={"SGA"=>false, "BINARY"=>false}, @sock=#<TCPSocket:fd 9, AF_INET, 192.168.179.127, 62469>>
irb(main):003:0> pop.login("hikaru", "パスワード")
=> "\nKernel 3.10.0-957.1.3.el7.x86_64 on an x86_64\nClara login: hikaru\nPassword: \nLast login: Sun Jan 13 23:18:59 from ::ffff:192.168.179.127\n<\xE6\x9C\xAC\xE7\x95\xAA hikaru 23:22:30> "
```

# 最後に
RubyのNet::Telnetを使用する場合は、上記のようにサーバ側のPS1を気にしないといけない。なお、普通のtelnetは違うルールでサーバのプロンプトを認識しているらしく、PS1の最後に半角スペースをいれなくても接続できた。

以上である。