恥は/dev/nullへ by 初心者

プログラミング素人がのろのろと学んだことをつづっています♪

bashはどの設定ファイルを読み込むのか?

この記事の内容はDebian11(bullseye)環境にもとづいています。

目次


事の始まり(.local/binにパスが通っていない?)

bashの設定ファイルがどんな風に読み込まれるのか理解していなかったので、以下の出来事に遭遇しました。

システム上にはaptでインストールしたVimがありましたが、それとは別に自分でソースからビルドしたVimを試すために、ビルドしたファイルを~/.local/binに置きました。というのも、以前いずれかの設定ファイルで

if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi

#(コメント)
# if文の意味は「ホームディレクトリに .local/binというディレクトリ
# がある場合、%PATHよりも~/.local/binを優先してパスを通す」です

を見た記憶があったからです。そこで、~/.local/binにビルドしたVimファイルを置いておけば、aptでインストールされたVim(/usr/binにあるVim)ではなく、ビルドしたVimが起動すると思っていたのです。

しかし、ターミナルを開いてVimを起動したところ、aptでインストールしたVimの方が起動しました。

調べてみると、上述したif文は~/.profileに記述されていました。記述があるのに~/.local/binにパスが通っていないということは、ターミナルを起動した時に~/.profileは読み込まれていないということです。そこで、bashの設定ファイルがどんな風に読み込まれるのかを調べてみました。


この記事のポイント

この記事のポイントは次の2つです。

(1) bashがログインシェルとして起動したのか、インタラクティブシェルとして起動したのか

(2) (1)に記した各ケースにおいて、どの設定ファイルが読み込まれるか


ログインシェルとは? インタラクティブシェルとは?

ネット情報とmanページによると、bashがログインシェルとして起動したのかインタラクティブシェルとして起動したのかによって、bashが読み込む設定ファイルが異なるようです。では、ログインシェルとインタラクティブシェルとは何でしょうか?

ログインシェルについては

ログインシェル(login shell)とは、0番目の引数の最初の文字が - であるシェル、または --login オプション付きで起動されたシェルのことです。

とmanページに書かれています。参考にしたサイトには、bashがログインシェルとして起動する例として以下のものが載っていました。

su - [user]
bash --login
ssh user@host

1行目が「0番目の引数の最初の文字が - である」例のようです。2行目は「--login オプション付きで起動された」例です。3行目はsshでログインしている場面ですから、そのままログインシェルですね。

一方、インタラクティブシェルについては

対話的なシェルとは、オプションでない引数がなく、標準入力と標準エラー出力がいずれも端末に接続されていて(中略)、-c オプションが指定されていない状態で起動されたシェル、または -i オプション付きで起動されたシェルのことです。

とmanページに書かれています。参考にしたサイトには、bashインタラクティブシェルとして起動する例として以下のものが載っていました。

su [user]
bash

1行目の例を見た時、manページの「オプションでない引数がなく」という記述との関係で、「『su ユーザー名』としてもインタラクティブシェルとして起動するのだろうか?」という疑問を抱きましたが、後述する動作確認の結果を見るとインタラクティブシェルとして起動することが分かります。*1


余談ですが、ログインシェルでもインタラクティブシェルでもないケース(?)もありそうです。


ログインシェルが読み込む設定ファイル

manページの

bashが対話的なログインシェルとして起動されるか、--loginオプション付きの非対話的シェルとして起動されると、/etc/profileファイルが存在すれば、bashはまずここからコマンドを読み込んで実行します。このファイルを読んだ後、bashは~/.bash_profile, ~/.bash_login, ~/.profileをこの順番で探します。bashは、この中で最初に見つかり、かつ読み込みが可能であるファイルからコマンドを読み込んで実行します。

という記述から

・bashがログインシェルとして起動した場合
・bashが --login オプション付きで起動した場合

には

/etc/profileが存在すれば、これを読み込む。

その次に、

~/.bash_profile
~/.bash_login
~/.profile

という順序で探していく

という流れであると分かります。

ここで注意しなくてはならないのが、manページの以下の記述です。

bashは、この中で最初に見つかり、かつ読み込みが可能であるファイルからコマンドを読み込んで実行します。

「最初に見つかり」と書かれているように、.bash_profile、.bash_login、.profileの3つに関しては、3つ全てが読み込まれるわけではないということです。

Debian11で実験した所、次のように動作していました。

# ステップ 1
 .bash_profileが存在すれば .bash_profileを読み込んで終了
 (.bash_loginと.profileは読み込まれない)
 .bash_profileが存在しない場合、ステップ 2へ進む

# ステップ 2
 .bash_loginが存在すれば .bash_loginを読み込んで終了
 (.profileは読み込まれない)
 .bash_loginが存在しない場合、ステップ 3へ進む

# ステップ 3
 .profileが存在すれば、.profileを読み込む

つまり、読み込まれるのは3つのうち1つだけです(これらのファイルのどれかに残り2つのファイルも読み込むような細工をしておけば話は別ですが)。


インタラクティブシェルが読み込む設定ファイル

bashインタラクティブシェルとして起動した場合に関してはmanページに次のように書かれています。

ログインシェルでない対話的シェルとして起動されると、 ~/.bashrcファイル があれば、bashはここからコマンドを読み込み、実行します。

つまり、

bashがインタラクティブシェルとして起動した場合
~/.bashrcファイルが存在すれば、これを読み込む

ということです。裏を返して言えば、.bashrcが読み込まれた場合、bashインタラクティブシェルとして起動したということになります。


動作確認の前提

ここまで述べてきたことについてDebian上で動作確認をすることにしました。

まず、各設定ファイルが読み込まれた時に、そのファイルが読み込まれたと分かるようにecho文を記述しました。

/etc/profileファイルに    echo '/etc/profile was read'   と記述
~/.bash_profileファイルに echo '.bash_profile was read'  と記述
~/.bash_loginファイルに   echo '.bash_login was read'    と記述
~/.profileファイルに      echo '.profile was read'       と記述

/root/.profileファイルに  echo '.profile(root) was read' と記述


また、~/.profileにおいて

if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
    fi
fi

という部分をコメントアウトしておきました。このままだと.profileが読み込まれた時に.bashrcも読み込まれるので、bashがログインシェルとして起動したのかインタラクティブシェルとして起動したのか紛らわしいからです。

同じ理由で、rootのホームディレクトリにある.profileにおいて

if [ "$BASH" ]; then
  if [ -f ~/.bashrc ]; then
    . ~/.bashrc
  fi
fi

という部分をコメントアウトしておきました。

同時に、rootのホームディレクトリにある.bashrcに次のecho文を記述しました。

echo '.bashrc(root) was read'


それと、shoptコマンドを使用します。

shopt login_shell

を実行すると、ログインシェルとして起動した場合に

login_shell      on

と表示されます。
ログインシェルとして起動していない場合は

login_shell      off

と表示されます。


なお、先述したとおり、.bashrcが読み込まれた場合はbashインタラクティブシェルとして起動したと判断できます。これらを元に動作確認してみました。


動作確認1(ファイルの読み込み順序)

動作確認1では、bashがログインシェルとして起動した場合に、/etc/profile、.bash_profile、.bash_login、.profileがmanページの記載通りに読み込まれていることを確認します。

ホームディレクトリに .bash_profileと.bash_loginは無く .profileだけがある状況でDebianからログアウトし、再度ログインしました。画面には次のように表示されました。

/etc/profile was read
.profile was read

まず、存在すれば必ず読み込まれる/etc/profileが読み込まれ、その後 .profileが読み込まれていることが分かります。

次に、ホームディレクトリに .profileと .bash_loginがある状況でDebianからログアウトし、再度ログインしました。画面には次のように表示されました。

/etc/profile was read
.bash_login was read

これを見ると、.profileよりも先に探される.bash_loginがあったので、.bash_loginだけが読み込まれ、.profileが読み込まれていないことが分かります。

今度は、ホームディレクトリに .profile、.bash_login、.bash_profileがある状況でDebianからログアウトし、再度ログインしました。画面には次のように表示されました。

/etc/profile was read
.bash_profile was read

一番最初に探される.bash_profileが読み込まれ、.bash_loginと.profileは読み込まれていないことが分かります。


動作確認2(ログインかインタラクティブか)

動作確認2では、bashがログインシェルとして起動したのか、インタラクティブシェルとして起動したのか、もしくは、どちらでもないのかを見てみます。


<説明>
以下の動作確認例において赤文字になっている部分が実行したコマンドです。
なお、ホームディレクトリには .profileだけがある状態です(.bash_profileと.bash_loginはありません)。

<su - を実行し、shoptコマンドを実行>
$ su -
パスワード: (ここでrootのパスワードを入力)
/etc/profile was read
.profile(root) was read
# shopt login_shell
login_shell     on

bash --loginを実行し、shoptコマンドを実行>
$ bash --login
/etc/profile was read
.profile was read

$ shopt login_shell
login_shell     on

この2つのケースではshoptコマンド実行後に「login_shell on」と表示されているので、bashがログインシェルとして起動したことが分かります。

<su を実行し、shoptコマンドを実行>
$ su
パスワード: (ここでrootのパスワードを入力)
.bashrc(root) was read

# shopt login_shell
login_shell     off

.bashrcが読み込まれていることから、bashインタラクティブシェルとして起動したことが分かります。

次は、「オプションでない引数がなく」というmanページの記述との関係で気になっていたケースですが・・・。

動作確認をするために、pandaという一般ユーザーを作り、pandaのホームディレクトリの.bashrcには

echo '.bashrc(panda) was read'

と記述しました。

<su [user] を実行し、shoptコマンドを実行>
$ su panda
パスワード:(ここでpandaのパスワードを入力)
.bashrc(panda) was read

$ shopt login_shell
login_shell     off

この結果を見ると、bashインタラクティブシェルとして起動していますね。

bash を実行し、shoptコマンドを実行>
$ bash
.bashrc was read

$ shopt login_shell
login_shell     off

bashインタラクティブシェルとして起動しています。


最後にログインシェルでもインタラクティブシェルでもなさそうな(?)ケースです。

manページには、インタラクティブシェルとして起動するケースの1つとして「-c オプションが指定されていない状態で起動されたシェル」が載っています。-c オプションを試すために以下のスクリプトを用意しました。

#!/bin/bash

echo 'hoge'
shopt login_shell

このスクリプトhoge.shというファイル名で保存し、以下を実行しました。

bashに -c オプションを指定して実行し、shoptコマンドを実行>
$ bash -c ~/hoge.sh 
hoge
login_shell     off

この結果を見ると、.bashrcが読み込まれていないのでインタラクティブシェルではありません。同時に、「login_shell off」とあることから、ログインシェルでもありません。


おまけ(.bash_logout)

manページに.bash_logoutというファイルがどのタイミングで読み込まれるのか記述されていました。

ログインシェルが終了するときには、~/.bash_logoutファイルがあれば、bashはこれを読み込んで実行します。

そこで .bash_logoutに以下の記述をして動作確認をしてみました。

date "+%Y/%m/%d %T" > logout_time.txt


bash --loginを実行し、exitでログアウト>
$ bash --login
/etc/profile was read
.profile was read

$ exit
$ cat logout_time.txt
2023/05/12 01:09:02

確かにログインシェルが終了する時に.bash_logoutが読み込まれています。

ついでに、Xを終了し、コンソールで

exit

と入力してログアウトしました。その後、再ログインしてlogout_time.txtを開いてみたら、ログアウトした時点の時刻が記録されていました。

一方、「sudo systemctl poweroff」を実行してシステムをシャットダウンした場合に関しては、.bash_logoutは読み込まれていませんでした(logout_time.txtに時刻が記録されていませんでした)。


参考にしたサイト

以下のサイトを参考にさせていただきました。

(1) ログインシェル、インタラクティブシェルの見分け方(bash) - 朝から昼寝
(2) ログインシェルとインタラクティブシェルと~/.bashrc達の関係 - Qiita


残った疑問

manページに次の記述があります。

bashが対話的に動作している場合には、PS1が設定され、$- に i が含まれます。これを利用すると、対話的動作の状態であるかどうかを、シェルスクリプトや起動ファイルの内部で調べられます。


動作確認をしていた時、shoptコマンドの結果が「login_shell on」であると同時に、「PS1が設定されている」または「$-に i が含まれている」というケースがありました(echo $PS1 や echo $- で確認)。

この場合、「ログインシェルであって、インタラクティブシェルではない」とも「インタラクティブシェルであって、ログインシェルではない」とも言えません。そこで、自分勝手な解釈ですが「bashはログインシェルとして起動したけれど、対話的に動作しているということかな?」と考えておきました。

言い換えると、「インタラクティブシェルとして起動した」ということと「対話的に動作している」ということは別なのかな、という考え方です。

この点は疑問として残ったままです。しかし、bashをツールとして使うという観点で言えば、どのケースでどのファイルが読み込まれるのかが分かれば良いので、これといって問題は無いんですけれどね。

*1:後ほど、参考にしたサイトの(1)を読み返したら、「オプションでない引数(スクリプトファイル等)」という記載がありました。