新しい開発環境を構築していて、 .bashrc
と .bash_profile
のどちらに何を書くべきなのか、今までは適当にやっていたがベストプラクティスを調べて準拠したくなり、気合を入れて調べたのでメモを残す。(macOS Venturaで確認)
調べたいこと
- bashの主要な設定ファイルである
.bashrc
と.bash_profile
は、それぞれどんなときに読み込み・実行されるのか確認し、整理する - 上記整理に従って、bashの設定の管理指針の明確化を図る
bashシェルのモード
bashのシェルには、起動方法によって「ログインシェル」と「インタラクティブシェル」があり、それぞれ読み込まれる設定ファイルが異なる。
「ログインシェル」は、主にシェルのセッション開始時に用いられる。具体的には、macOSでターミナルを立ち上げたり、SSHでリモートログインしたときなどである。一番「外側のシェル」と考えておけば良いだろう。逆に、シェルの中でさらにbash
コマンドなどでシェルを開始した場合などは「非ログインシェル」となる。
「インタラクティブシェル」は、ユーザーがインタラクティブに操作可能な(具体的には標準入力と標準エラー出力がターミナルに接続された)状態で起動したシェルを指す。逆に、bash -c date
や bash foo.sh
のように、コマンドやシェルスクリプトを実行する場合は「非インタラクティブシェル」となる。
これらふたつの概念は直交しているので、ログインシェルかつインタラクティブシェル(インタラクティブ・ログインシェル)、あるいは非ログインシェルかつ非インタラクティブシェル(非インタラクティブ・非ログインシェル)という状態も取りうる。
確認方法
そのシェルが ログインシェル であるかどうかは、shopt -q login_shell
の終了コードが0かどうかで判別できる。また、インタラクティブシェル であるかを確認するには、bashの起動フラグが格納されている $-
変数に i
が含まれているかどうかで判別できる。
$ bash --login -i # ログインシェルかつインタラクティブシェルでbashを起動
$ echo $-
himBH # iが含まれている => インタラクティブシェル
$ shopt -q login_shell
$ echo $?
0 # 直前の終了コードが0 => ログインシェル
各起動モードでの設定ファイルの実行パターン
さて、manによれば、上記のそれぞれのシェルでの設定ファイルの読み込み・実行パターンは以下の定義されている。
モード | 設定ファイルの実行 |
---|---|
インタラクティブ・ ログインシェル |
① /etc/profile を実行する ② ~/.bash_profile, ~/.bash_login, ~/.profile の順で最初に見つかったファイルひとつを実行する |
インタラクティブ・ 非ログインシェル |
~/.bashrc を実行する |
非インタラクティブシェル | デフォルトでは何も実行しない($BASH_ENV があれば、指定された名前のファイルを実行する) |
各設定ファイルごとの早見表にすると、こうなる。
インタラクティブ・ログインシェル | インタラクティブ・非ログインシェル | 非インタラクティブシェル | |
---|---|---|---|
/etc/profile | ○ | ||
~/.bash_profile ~/.bash_login ~/.profile いずれかひとつ |
○ | ||
~/.bashrc | ○ | ||
$BASH_ENV | ○ |
アプリケーション経由でbashを起動するときの挙動
次に、各アプリケーションや環境でbashを使った時に、実際にどのモードが使われるか確認していく。
macOSのTerminal.app
macでターミナルを立ち上げると、 インタラクティブ・ログインシェル が開始する。読み込まれるのは .bash_profile
。
SSH
SSHリモートログイン
SSHでリモートホストにログインすると、リモートホストのbashは インタラクティブ・ログインシェル が開始する。読み込まれるのは .bash_profile
。
SSHリモートコマンド/SCP
SSHリモートコマンド実行(例: ssh localhost hostname
、またはscp
)の際にはリモートホストで 非インタラクティブ・非ログインシェル が開始する。
しかし、bashはリモートシェルでの実行を検知すると .bashrc
を実行する仕様になっている。
(man bash)
Bash attempts to determine when it is being run by the remote shell daemon, usually rshd. If bash determines it is being run by rshd, it reads and executes commands from ~/.bashrc, if that file exists and is readable.Bashは、リモートシェルデーモン(通常はrshd)によって実行されているかどうかを判断しようとします。bash が rshd によって実行されていると判断した場合、~/.bashrc が存在し、そのファイルが読める場合は、そのファイルからコマンドを読み込んで実行する。
この仕様によって、 .bashrc
にechoが含まれていると問題が起きるので注意すること。
screen
screenで新規ウィンドウを作成すると、 インタラクティブ・ログインシェルが開始する。実行されるのは .bashrc
である。
tmux
tmuxで新規ペインを作成すると、デフォルトでは インタラクティブ・ログインシェル が開始する。そのため、実行されるのは .bash_profile
である。
後で述べるように、この挙動はやや都合が悪いので、 .tmux.conf
に以下の設定を追加することで インタラクティブ・非ログインシェル に変更することができる。こうすると .bashrc
が実行されるようになる。
set -g default-command "${SHELL}"
管理方針
ここまでで、bashの主な起動モードや実行される設定ファイル、各アプリケーションからシェルを立ち上げたときの挙動を大まかに確認できた。
インタラクティブ・ログインシェル (.bash_profile) | インタラクティブ・非ログインシェル(.bashrc) | 非インタラクティブシェル(実行なし) | |
---|---|---|---|
macOS Terminal新規ウィンドウ | ○ | ||
GNOMEデスクトップログイン時(らしい) | ○ | ||
SSHリモートログイン | ○ | ||
SSHリモートコマンド,SCP | ○(※1) | ||
bash(シェル起動) | ○ | ||
bash(コマンド実行) | ○ | ||
screen | ○ | ||
tmux | ○ (デフォルト時) |
○ (default-command設定時) |
※1: モード自体は非インタラクティブだが、前述の通りbashのリモートシェル仕様によってインタラクティブ相当の動作をする
では、これを踏まえてbashの設定ファイルの管理方針を検討しよう。
一般的に、.bash_profile
にはそのセッションにおいて一度だけ実行したい設定を、 .bashrc
にはシェルを開始するごとに毎回実行したい設定を記述するべきと言われている。
シェルの中でシェルを開始した場合、環境変数は内側のシェルに引き継がれる。そのため、シェルを起動するごとに実行される .bashrc
に export PATH=/path/to/bin:$PATH
といった環境変数の追加を記述してしまうと、シェルを立ち上げるごとにPATHが長くなってしまう。それで誤動作が起きるケースは稀だが、ともあれそういった理由で環境変数の設定の記述は .bash_profile
が適しているとされている。
一方で、シェルのエイリアスや関数の定義などは、内側のシェルには引き継がれない。これらの設定を ログインシェルでしか実行されない .bash_profile
に記述してしまうと、内側のシェルでエイリアスなどが使えなくなってしまうので、エイリアスや関数は .bashrc
で定義するのが適しているとされている。
この一般的な方針を採用しても問題なさそうである。ただし、二つだけ注意することがある。ひとつは、単純にそのように書き分けただけではターミナルを立ち上げた(ログインシェル)際に .bashrc
が実行されず、エイリアスや関数が定義されないので、.bash_profile
から .bashrc
をsourceしてやる必要がある。
[[ -f ~/.bashrc ]] && source ~/.bashrc
もう一つはtmuxである。先ほど少し述べたように、デフォルトではtmuxはインタラクティブ・ログインシェルを起動してしまう。しかし、ユーザーは通常 bash などのシェルから tmux を開始するので、これではログインシェルが2階層になってしまう。これによって、本来シェルのセッション中一度だけ実行したい .bash_profile
が2回実行され、PATHが長くなってしまうなどの問題として現れる。そこで、インタラクティブ・ノンログインシェル を起動させるため、 tmux.conf
で設定を変更する必要がある。
set -g default-command "${SHELL}"
これで「.bash_profileはセッション開始時に最初に一度だけ実行される、.bashrcはシェル開始時に毎回実行される」という原則へのコンプライアンスが得られた。
まとめ
.bash_profile
はセッション開始時に一度だけ実行される。 .bashrc
はシェル開始時に毎回実行される。(ただしtmuxのように一部この原則に準拠しないアプリケーションがあるので対処が必要)
本稿で私が勧めるbashの設定ファイルの管理方針は、.bash_profile
で環境変数を設定して.bashrc
をsourceする、 .bashrc
でエイリアスや関数を設定すること