背景
いちいち設定しなくても色々うまいことやってくれるということで、zsh から fish に乗り換えて快適なシェル環境で満足していたところ、当該マシンへの rsync でエラーに遭遇しました。
> rsync -av -e ssh src remote-host:dst
protocol version mismatch -- is your shell clean?
(see the rsync man page for an explanation)
rsync error: protocol incompatibility (code 2) at compat.c(174) [sender=3.1.0]
この問題はログインシェルを一時的に bash に戻すことで解決できました。
ですが、rsync に限らず、何らかの形でシェルに依存しているプログラムは、そのシェルが POSIX 互換であることを前提としています。fish は POSIX 非互換なので、いつまた同じような問題に遭遇するかわかりません。
解決策
ログインシェルは bash のままで、インタラクティブシェルのときだけ exec fish
することで、普段使いのシェルには fish を利用できます。.bashrc
の冒頭に以下の case
ブロックを足すだけです。
# If not running interactively, don't do anything
case $- in
*i*) exec fish;;
*) return;;
esac
条件分岐は必要?
これで問題は解決しました。
ですが、ここまで読んできて気付いた人もいるかと思います。
そもそも、なぜ .bashrc
で条件分岐が必要なのでしょうか。sudo
はじめ bash を非インタラクティブモードで起動する場合は .bashrc
がロードされることはありません。シンプルに .bashrc
でいきなり exec fish
でいいように思います。
$ head -n1 .bashrc
echo "I am .bashrc"
$ bash -c 'echo hello'
hello
実際に試してみても .bashrc
はロードされていないことが確認できます。
ssh 時の bash 不思議仕様
ところが ssh に限っては、ssh <remote-host> <command>
の形式で非インタラクティブに実行しても .bashrc
がロードされるという不思議仕様となっています。
確認してみましょう。
$ ssh localhost 'echo $-'
hBc
-
変数の値を確認すると、ssh でコマンドを実行した場合は確かに非インタラクティブモードになっています。では、先ほど設定した .bashrc
の冒頭でメッセージを出力する状態で ssh を実行してみましょう。
$ ssh localhost 'echo $-'
I am .bashrc
hBc
確かに .bashrc
がロードされていることが確認できました。
ssh で非インタラクティブにコマンドを実行するときに限り bash は特別に .bashrc
をロードするようになっています。このあたりの挙動については、bash の設定ファイル読み込み仕様が詳しいので参照してみてください。