.bashrc で exit するスクリプトを source しちゃったときの対処法

# tl;dr

* `source` で読み込んだスクリプト内で `exit` すると、呼び出し元スクリプトごと終了する
* ↑ を .bashrc でやってしまうとログインができなくなる
* そうなってしまったらログイン時に Ctrl+C を連打する

# 背景

自作の便利スクリプトをサーバーログイン時 (厳密には Bash 起動時) に実行させるようにしました。

```bash
[togawa@remote01 ~]$ echo 'source ./myscript.sh' >> ./.bashrc
```

このサーバーを *remote01* とし、ログイン元サーバーを *local01* とします。

# 問題

一度 remote01 からログアウトし、再度ログインしたところ **そのままログアウト** されてしまいました。

```bash
[togawa@remote01 ~]$ logout
Connection to xxx.xx.x.xx closed.

[togawa@local01 ~]$ ssh remote01
togawa@xxx.xx.x.xx's password: 
Last login: Fri Feb 10 19:04:02 2017 from xxx.xx.x.xx
Connection to xxx.xx.x.xx closed.
[togawa@local01 ~]$ 
```

原因は以下の2つが成り立ってしまったためです。

* スクリプトを .bashrc の `source` から呼び出していること
* スクリプト中で `exit` していること


# 試したこと

## ssh rm

このままではサーバーにログインができません。
とはいえ、 .bashrc が発動する前に問題のスクリプト (myscript.sh) を消せれば良いはずです。
`ssh` に引数を与えるとそれをコマンドとして実行してくれるので、 `rm` を渡してみます。

```bash
[togawa@local01 ~]$ ssh remote01 rm myscript.sh
togawa@xxx.xx.x.xx's password: 
Connection to xxx.xx.x.xx closed.
[togawa@local01 ~]$ 
```

結果はだめ。

## scp

ssh がだめなら scp はどうだ、ということで空のファイルを転送してスクリプトを 0 byte にする作戦です。

```bash
[togawa@local01 ~]$ touch myscript.sh
[togawa@local01 ~]$ ls -l myscript.sh
-rw-r--r-- 1 togawa togawa 0  2月 10 19:48 myscript.sh

[togawa@local01 ~]$ scp ./myscript.sh remote01:~
togawa@xxx.xx.x.xx's password: 
Connection to xxx.xx.x.xx closed.
[togawa@local01 ~]$ 
```

これもだめでした。 ssh/scp では律儀に .bashrc が読み込まれてしまいます。

## Bash の起動を止める

ServerFault という Stackoverflow 風味のサイトでは、「.bashrc がロードされる前に `Ctrl + C` で Bash の起動を中止させるといいよ」という回答がありました。
http://serverfault.com/questions/206544/i-screwed-up-exit-in-bashrc

`ssh` 実行 → ログイン先サーバーで bash 起動 → (＊) → .bashrc ロード
↑ の (＊) のタイミングで `Ctrl + C` を叩け、とのこと。タイミングがシビアそう。

`ssh` にデバッグモードの `-v` オプションをつけてやってみました。
確かにコツが必要でしたが、成る程、何度かやったらうまくいきました。

```bash

[togawa@local01 ~]$ ssh -v remote01
OpenSSH_4.3p2, OpenSSL 0.9.8e-fips-rhel5 01 Jul 2008
debug1: Reading configuration data /home/togawa/.ssh/config
debug1: Applying options for remote01
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Applying options for *
debug1: Connecting to xxx.xx.x.xx [xxx.xx.x.xx] port 22.
debug1: Connection established.
debug1: identity file /home/togawa/.ssh/identity type -1
debug1: identity file /home/togawa/.ssh/id_rsa type 1
debug1: identity file /home/togawa/.ssh/id_dsa type -1
debug1: loaded 3 keys
debug1: Remote protocol version 2.0, remote software version OpenSSH_6.6.1
debug1: match: OpenSSH_6.6.1 pat OpenSSH*
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_4.3
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: server->client aes128-ctr hmac-md5 none
debug1: kex: client->server aes128-ctr hmac-md5 none
debug1: SSH2_MSG_KEX_DH_GEX_REQUEST(1024<1024<8192) sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_GROUP
debug1: SSH2_MSG_KEX_DH_GEX_INIT sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_REPLY
debug1: Host 'xxx.xx.x.xx' is known and matches the RSA host key.
debug1: Found key in /home/togawa/.ssh/known_hosts:42
debug1: ssh_rsa_verify: signature correct
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: SSH2_MSG_SERVICE_REQUEST sent
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Trying private key: /home/togawa/.ssh/identity
debug1: Offering public key: /home/togawa/.ssh/id_rsa
debug1: Authentications that can continue: publickey,password
debug1: Trying private key: /home/togawa/.ssh/id_dsa
debug1: Next authentication method: password
togawa@xxx.xx.x.xx's password: 
debug1: Authentication succeeded (password).
debug1: channel 0: new [client-session]
debug1: Entering interactive session.
debug1: Sending environment.
debug1: Sending env LANG = ja_JP.UTF-8
Last login: Fri Feb 10 19:30:13 2017 from xxx.xxx.x.xx
^C-bash-4.2$ ^C
-bash-4.2$ ^C
-bash-4.2$ ^C
-bash-4.2$ ^C
-bash-4.2$ ^C
-bash-4.2$ 
-bash-4.2$ ls
myscript.sh
-bash-4.2$ rm myscript.sh 
-bash-4.2$ ls
-bash-4.2$ 
-bash-4.2$ 
```

Bash キャンセル (各ゲーっぽい) が成功すると `$PS1` が読み込まれていない、すなわち .bashrc が読み込まれていないことがわかります。
これによりログイン先サーバーで操作が可能になるので、問題のスクリプトを `rm` すれば万事解決です。

# 教訓

シェルスクリプトではむやみに `source` を使わない。