多段SSH（ProxyCommand不使用時）の深さを確認する方法とその応用

踏み台サーバーを複数経由してSSH接続する場合、sshした先でまたsshして…と入れ子になり深くなっていく。以下の例だと3段。

```text
[ local ]
    │ ssh(1)
    │
    └─> [ remote(1) ]
            │ ssh(2)
            │
            └─> [ remote(2) ]
                    │ ssh(3)
                    │
                    └─> [ remote(3) ]
```

このとき、ログインしたまま入れ子の深さを確認するには以下のようにする。

0. 適当に文字を打ってみて、端末に表示される（応答がある）ことを確認しておく
0. <kbd><kbd>Enter</kbd></kbd> を打つ
0. <kbd><kbd>~</kbd></kbd> を回数を数えながら何度か打つ
0. 端末に表示された `~` を数える
0. `~` の「打った数 － 表示された数」がSSHの深さ<br>例では5回打てば2つ表示され、3段と判定できる

## 仕組み
sshのマニュアルを読むか、ssh中に <kbd><kbd>Enter</kbd> <kbd>~</kbd> <kbd>?</kbd></kbd> と打てばタネがわかる。

```text
Supported escape sequences:
 ~.   - terminate connection (and any multiplexed sessions)
 ~B   - send a BREAK to the remote system
 ~C   - open a command line
 ~R   - request rekey
 ~V/v - decrease/increase verbosity (LogLevel)
 ~^Z  - suspend ssh
 ~#   - list forwarded connections
 ~&   - background ssh (when waiting for connections to terminate)
 ~?   - this message
 ~~   - send the escape character by typing it twice
(Note that escapes are only recognized immediately after newline.)
```

今回重要なのは最後の２行。冒頭の入力手順では以下のような処理が起きているのだろう。

| 入力 | ssh(1) | ssh(2) | ssh(3) | 出力 |
|:-:|:-:|:-:|:-:|:-:|
| <kbd><kbd>Enter</kbd></kbd> | newlineを送信<br>エスケープシーケンスを警戒 | newlineを送信<br>エスケープシーケンスを警戒 | newlineを送信<br>エスケープシーケンスを警戒 | remote(3)<br>newlineを反映 |
| <kbd><kbd>~</kbd></kbd> | エスケープシーケンスの可能性あり<br>送信保留 |   |   |   |
| <kbd><kbd>~</kbd></kbd> | エスケープシーケンスに一致<br>`~` を送信 | エスケープシーケンスの可能性あり<br>送信保留 |   |
| <kbd><kbd>~</kbd></kbd> | `~` を送信 | エスケープシーケンスに一致<br>`~` を送信 | エスケープシーケンスの可能性あり<br>送信保留 |   |
| <kbd><kbd>~</kbd></kbd> | `~` を送信 | `~` を送信 | エスケープシーケンスに一致<br>`~` を送信 | remote(3)<br>`~` を反映 |
| <kbd><kbd>~</kbd></kbd> | `~` を送信 | `~` を送信 | `~` を送信 | remote(3)<br>`~` を反映 |

途中のsshがひとりひとつずつ `~` を奪ってしまうので、sshの段数だけ端末に表示されなくなる。

## 応用
この `~` がどう扱われるか知っておけば色々な操作ができるようになる。

例えばsshの応答がなくなってすぐ切断したいとき、 <kbd><kbd>~</kbd></kbd> を適切な回数だけ重ねることで**狙った深さのsshにコマンドを送れる**。そうすれば踏み台全てからログアウトして入り直しという手間は無くなる。

最初の例で ssh(3) を終了したければ、 <kbd><kbd>Enter</kbd> <kbd>~</kbd> <kbd>~</kbd> <kbd>~</kbd> <kbd>.</kbd></kbd> とチルダを3回重ねてからコマンドに対応する文字を打てばいい。

| 入力 | ssh(1) | ssh(2) | ssh(3) | 出力 |
|:-:|:-:|:-:|:-:|:-:|
| <kbd><kbd>Enter</kbd></kbd> | newlineを送信<br>エスケープシーケンスを警戒 | newlineを送信<br>エスケープシーケンスを警戒 | newlineを送信<br>エスケープシーケンスを警戒 | remote(3)<br>応答なし |
| <kbd><kbd>~</kbd></kbd> | エスケープシーケンスの可能性あり<br>送信保留 |   |   |   |
| <kbd><kbd>~</kbd></kbd> | エスケープシーケンスに一致<br>`~` を送信 | エスケープシーケンスの可能性あり<br>送信保留 |   |
| <kbd><kbd>~</kbd></kbd> | `~` を送信 | エスケープシーケンスに一致<br>`~` を送信 | エスケープシーケンスの可能性あり<br>送信保留 |   |
| <kbd><kbd>.</kbd></kbd> | `.` を送信 | `.` を送信 | エスケープシーケンスに一致<br>**SSHを切断** | remote(2)<br>復帰 |

実は remote(2) が既に応答なしという場合もあるが、そしたらチルダを1つ減らして再入力して ssh(2) を切断すればいい。

個人的によくやっているのは、この切断以外に**サスペンド**がある。作業先からログアウトしたくはないが踏み台に戻って少し作業したいときがあるため。

## その他
普段は入力がエスケープシーケンスに一致してしまうことが滅多になく、 `~` が一時的に出力されていない場合も気付いていないと思う。 <kbd><kbd>Enter</kbd></kbd> の直後に <kbd><kbd>~</kbd></kbd> を入力するとしたら、例えばホームディレクトリ以下のスクリプトを実行するときだが、エスケープシーケンスに一致しなければ保留中のチルダごとまとめて送信される。

| 入力 | ssh(1) | ssh(2) | ssh(3) | 出力 |
|:-:|:-:|:-:|:-:|:-:|
| <kbd><kbd>Enter</kbd></kbd> | newlineを送信<br>エスケープシーケンスを警戒 | newlineを送信<br>エスケープシーケンスを警戒 | newlineを送信<br>エスケープシーケンスを警戒 | remote(3)<br>newlineを反映 |
| <kbd><kbd>~</kbd></kbd> | エスケープシーケンスの可能性あり<br>送信保留 |   |   |   |
| <kbd><kbd>/</kbd></kbd> | エスケープシーケンスに不一致<br>**`~/` を送信** | エスケープシーケンスに不一致<br>**`~/` を送信** | エスケープシーケンスに不一致<br>**`~/` を送信** | remote(3)<br>**`~/` を反映** |

多段に限らないが、少し怖いのは `~v` といったエスケープシーケンスがあること。bashのチルダ展開ではユーザー名を指定するとそのユーザーのホームディレクトリを表せるので、例えば `~vervet/script.sh` を実行しようとして下手に（というより最も迷いなく）入力すると、「 ssh(1) のログレベルを上げて」「`ervet/script.sh` をSSH先で実行する」という動作になってしまう。