接続先サーバのファイルに手を付けずに、sshで接続したインタラクティブシェルで自動的にset -uする #bash #ssh
こんにちは、ターミナル住人の平野です。
シェルスクリプトの先頭には原則必ずset -eu
をつけましょうね、
というのはいろんな所で言及されているので見たことがある人も多いと思います。
このうちset -u
ですが、変数が未定義の時に想定外の挙動になってしまうことが防げるので、
慎重に操作をするような場面では、インタラクティブシェルでも有効にした方がいい場面があります。
sshでEC2にログインした時これを行いたいとして、ログイン後にset -u
と毎回やるのは面倒です。
もちろんこう言った設定は.bashrc
に書くというのが定石ですね。
しかしバッチサーバー用途のインスタンスなどはユーザを共通で使用することも多く、
そこにある.bashrc
を変更してしまうことには抵抗があるという場合も多いと思います。
sshで接続するクライアント側だけの設定で上記ができないかと思い調べてみました。
結論: .ssh/config
の設定
.ssh/config
に設定を書く場合、
RemoteCommandを使って以下のように書くことで目的を達成することができました。
20201126 修正
--login
でログインをつける方法では、set -u
が正しく効いていませんでした。
ひとまず--login
なしで、明示的に.bash_profile
を読むことで回避するものを掲載してあります。
Host HogeHogeServer User ec2-user Hostname xxx.xxx.xxx.xxx IdentityFile /path/to/key.pem RequestTTY yes RemoteCommand exec bash --init-file <(echo 'source ~/.bash_profile && set -u')
試行錯誤
RemoteCommandを使えばsshでログインした後のコマンドを指定できるという情報はすぐ見つけることができたのですが、
RemoteCommandは普通、指定された処理が終了したらsshのセッションを終了してしまい、インタラクティブモードには入りません。
なので、最後にbash
をおくことで、明示的にインタラクティブシェルを起動するようにします。
こうすることで、コマンドを実行させつつ、単純なインタラクティブシェルへの接続のように見せかけることができます。
なお、この際RequestTTY yes
という文言が必要なようです。
これがないとインタラクティブシェルは起動しているのですが、その画面が出力されないという状況になってしまいます。
さて、ここまでの知識で、まずはディレクトリの移動で試してみたいと思います。
RemoteCommand cd / && bash
[ec2-user@ip-1-0-0-187 /]$
確かに/
をカレントディレクトリとしたインタラクティブシェルが起動しました。
次に目的のset -u
をやってみます。
RemoteCommand set -u && bash
echo $aaa/bbb # 出力: bbb
ダメですね。。。set -u
はbashの中には引き継がれないようです。
インタラクティブシェルについて
最後のbash
が何をしているかというと、これはサブプロセスとして新しくbashを起動しているということです。
そして、サブプロセスに引き継がれる情報は、基本的には環境変数だけです。
set -u
で設定した内容は環境変数ではない部分で管理されているようなので、サブプロセスには引き継がれません。
実際env
コマンドで環境変数を見てみても、set -u
する前後で環境変数に違いは何もありません。
ということで、サブプロセスに引き継げない以上、 新しく立ち上がったプロセス内で設定するしかないということになります。
bash
コマンドはそこにオプションを指定することができます。
なので、安直にbash -u
として起動してみればいい、という話になります。
実際やってみます。
RemoteCommand bash -u
bash: PROMPT_COMMAND: 未割り当ての変数です bash: COLORTERM: 未割り当ての変数です bash: local256: 未割り当ての変数です bash: BASH_COMPLETION_COMPAT_DIR: 未割り当ての変数です bash: USER_LS_COLORS: 未割り当ての変数です bash-4.2$ echo $aaa/bbb bash: aaa: 未割り当ての変数です
後半の方で、set -u
がきちんと有効になっていることが確認できました。
しかしその上の表示がめちゃめちゃ気になりますよね。
これは、このbash自体が最初からset -u
をつけて実行されているので、
/etc/bashrc
などの設定を読み込む際にもset -u
が有効になってしまっているためです。
未定義変数が使われている箇所で警告が出てしまい、実際いくつかの変数が適切に設定されていないという状況になってしまいました。
さすがにこの状況ではset -u
が自動でセットされたにしても代償が大きすぎます。
何か他の方法を考えないといけません。
--init-file
が勝利の鍵
「新しく立ち上がったプロセスの設定」と言ったら基本的には.bashrc
に書くというのが鉄則です。
しかし今回は.bashrc
は一切手を入れないということが前提条件ですから、これを変えるわけにはいきません。
そこで使用するのが --init-file
オプション1です。
これは、.bashrc
の代わりに読み込む設定ファイルを明示的に指定するオプションです。
これを使えば.bashrc
を書き換えることなく、目的の設定を読み込ませることができます。
以下のようにやってみます。
RemoteCommand bash --init-file <(echo 'set -u')
ここで出てきた<(echo 'set -u')
はプロセス置換というもので、
本来であればファイル名を指定する箇所にコマンドの出力を流し込むことができます。
これによって接続先のサーバーにファイルを一切置く必要がなくなるようにできます。
さて、これで試してみます。
echo $aaa/bbb bash: aaa: unbound variable
set -u
がきちんと適用されていますね!!!
.bashrc
は必ず読むようにする
気をつけなければいけないのは、--init-file
で指定したファイルは.bashrc
の代わりに読み込まれるということです。
つまり、上記設定では.bashrc
が読み込まれないということです。
これはまずいので、これも対策します。
ただやり方は簡単です。set -u
する前に.bashrc
を読めばいいので、こんな感じにすればいいことになります。
RemoteCommand bash --init-file <(echo 'source ~/.bashrc && set -u')
これで.bashrc
の読み込み後set -u
するという目的が達成できました!!
体裁を整える
最後にちょっとブラッシュアップします。
まずsshでインタラクティブシェル接続した場合はログインになりますので、--login
をつけた方が適切です。
でないと.bash_profile
が読まれません。
20201126 修正
--login
でログインをつける方法では、set -u
が正しく効いていませんでした。
これ以下の文章は現在修正中です。
また、bash
とだけ書くとインタラクティブシェルはサブプロセスとして開始されますので、
exec bash
とすることで現行のプロセス自体を新しいbashに置き換えるようにします。
ということで完成したものが、最初にも示した、以下のような設定となります。
RemoteCommand exec bash --login --init-file <(echo 'source ~/.bashrc && set -u')
読み込まれる設定の確認
/etc/profile
, /etc/bashrc
, ~/.bash_profile
, ~/.bashrc
のファイルに、
それぞれ実行されたら自分の名前をecho
させるようにした上で、
通常のsshログインと、上記RemoteCommandを経由したログインを比較してみます。
通常のsshログインの場合
/etc/profile .bash_profile .bashrc /etc/bashrc
RemoteCommandを経由したログイン
.bashrc /etc/bashrc /etc/profile .bash_profile .bashrc /etc/bashrc
通常ログインに比べて先頭に.bashrc
, /etc/bashrc
が余分についていますが、
それ以降は通常ログインと同じ順番で設定ファイルが読まれていますので実害はないものと考えられます。
まとめ
sshで接続したインタラクティブシェルで、.bashrc
などを読んだ後に自動的にオプション設定することに成功しました!
.bashrc
などの接続先のファイルに一切影響を与えずに設定ができるので、俺俺設定などしたい時にも役立つかもしれません(やりすぎ注意)。
--init-file
オプションを使えば、 sshに限らず、
「インタラクティブシェル起動したら最初にこのコマンド実行して欲しいんだけど...」な場合にある程度広く対応できそうです。
シェルに関しては調べれば調べるほど、何も知らんかったという情報が出てきて興味が尽きません! 以上何かのご参考になれば幸いです!
参考リンク
- BASH
- 接続先ごとにプロンプトを切り替える - khasegawa.net
- 覚えておきたいbashシェルスクリプトのオプション - インフラエンジニアWAY
- What is the difference between --init-file and --rcfile? - askubuntu
-
これは
--rcfile
オプションと全く同じようです。命名規則などの理由から--init-file
という別名が作られたようです。 ↩