ログインシェルは bash のまま fish を利用する

  • 15
    いいね
  • 0
    コメント

背景

いちいち設定しなくても色々うまいことやってくれるということで、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 の設定ファイル読み込み仕様が詳しいので参照してみてください。