.bash_profileと.bashrcなんて使い分けなくてよかったんや!

この記事の内容

.bash_profile.bashrc の使い分けの説明はいろいろあるんですが、
どうにも腑に落ちなかったので、私なりに調べてみました。
で、自分的にはこれで必要十分な理解ができたと思ったので投稿。
とりあえずここまで理解すれば、後はその都度対処していけるかなー、と。

スーパー忙しい人のための結論

設定はすべて.bashrcに書く
これでいいんやで。

動作確認の環境

  • Windows10のHyper-V上で動いているCentOS7で動作を確認
  • CentOS7はインストール直後の状態で試しているので設定ファイル等は書き換えていない
    • OSインストールのグループ(最小限とかから選ぶやつね)は「GNOME Desktop」を選択
  • Ubuntuなどのディストリビューションでは動作が異なるかもだけど、今回は言及しない
    • ここの説明を理解すれば、他のディストリビューションにもすぐに応用できると思う

基本動作のおさらい

初期ファイルの内容

まずはCentOS7インストール直後の.bash_profile.bashrcの中身を見てみます。
まずは.bash_profile

~/.bash_profileの初期状態。ただしコメントを消し、検証用コードを追加。
echo "bash_profile 1"                   #検証用に追加
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH
echo "bash_profile 2"                  #検証用に追加

.bash_profileの中で明示的に.bashrcを呼んでいるのがイヤらしいです。
それ以外は、PATHにユーザのHOME下のディレクトリを追加するだけです。
続いて.bashrc

~/.bashrcの初期状態。ただしコメントを消し、検証用コードを追加。
echo "bashrc 1"                  #検証のために追加
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi
echo "bashrc 2"                  #検証ために追加

こちらは共通設定ファイルである /etc/bashrc を読み込むだけです。
/etc/bashrcはrootが所有するファイルで、全ユーザ共通の設定が書かれています。
このファイルに関しては、ちゃんと呼ぶ設定になっている必要はありますが、
中を見る必要はないですから、この記事では基本的に一切触れません。

ssh接続による起動

次に、実際にbashを起動するためにsshで他のホストから接続してみます。

当該環境にssh接続する
[user-hoge@otherhost ~]$ ssh testhost
user-hoge@testhost's password:
Last login: Tue Mar 13 00:22:46 2018 from ::1
bash_profile 1
bashrc 1
bashrc 2
bash_profile 2
[user-hoge@testhost ~]$

接続に成功するとbashが起動し、まず .bash_profileが呼ばれ、
その内部で.bashrcが読まれている
ことがわかります。

ここまではLinuxを少し触ったことがある方にとって、共通認識と言って良いかと思います。

なお、ここで.bash_profile.bashrcの両方が読み込まれることが
「どっちに設定を書いたらいいんだ!?」と混乱してしまう元凶なんだと思います。
そして実際、「TeraTermで接続していくつかコマンドを実行するだけ」というユーザであれば、
この2つのファイルを区別する必要はありません。
自分の設定を加えたいのであれば、好きな方のファイルに書いてください。
なおその際、.bashrcを選んだ方が心安らかでいられる確率が高いことは付け加えておきます。

.bash_profileと.bashrcで差が出るタイミング

他の起動方法での挙動

sshで接続する以外にもbashを起動する場面をいくつか試してみます。

いろいろなbash起動
[user-hoge@testhost ~]$ bash -c "pwd"   #pwdコマンドを実行して終了する
/home/user-hoge
[user-hoge@testhost ~]$ bash pwd.sh     #"pwd"とだけ書いたシェルスクリプトを実行
/home/user-hoge
[user-hoge@testhost ~]$ bash            #新しいbashの対話シェルを立ち上げる
bashrc 1
bashrc 2
[user-hoge@testhost ~]$ exec bash       #新しいbashの対話シェルに切り替える
bashrc 1
bashrc 2

前半の2つのように、決められたコマンドを実行するだけの時は何も読まれませんが、
後半の2つのように、 「対話シェル」を起動する際には.bashrcが読まれます
そしてこの時は.bash_profileは読まれず、.bashrcが直接呼び出されています。

なお、勢いで伝わるかと思いますが、「対話シェル」とは、
何のコマンドを実行する? -> コマンド入力 -> コマンド実行 -> 何のコマンドを実行する? -> (繰り返し)
というやつです。

manを見てみる

.bashrcが対話シェル起動時に呼ばれることは、manにはっきりと書かれています。

~/.bashrc
対話シェルごとに実行される、個人用の起動ファイル。

とりあえず、ここに書かれていることと、我々の認識はあってる気がします。
ついでなので、続けて.bash_profileも見てみます。

~/.bash_profile
個人用の初期化ファイル。ログインシェルが実行します。

こちらはわかったような、わからないような。
とりあえず、.bashrcと違って、 対話シェルの開始とは関係がなさそう なことがわかりました。

ということで、ここら辺までが、ちょっと調べたことがある人ならよく知っている情報かと思います。
で、.bashrcが対話シェルの起動時に読み込まれることから、
.bashrcには対話シェルでだけ使うaliasや、キーバインドなんかを書いて、
.bash_profileには、対話シェルに限らず利用する環境変数なんかを書いている人が多いかと思います。
(基本的にこれで間違いじゃないです)

.bashrc存在意義の破綻!?

sshでのコマンド実行

.bashrcは対話シェルの起動時に読み込まれるものなので、
対話シェルだけで使用するような設定を書くっぽい、というのがここまでの話でした。

しかし、何故かそこに反旗を翻す輩が…。

sshに実行コマンドを指定した場合
[user-hoge@testhost ~]$ ssh localhost pwd #ssh接続した先のホストでpwdを実行
bashrc 1
bashrc 2
/home/user-hoge

これは由々しき事態です。
「対話シェルごとに実行される」はずの.bashrcが、コマンドを実行するだけなのに呼ばれています!
これはバグなんでしょうか?
しかし悲しい哉、manにもこの仕様が書かれています。

bash は、リモートシェルデーモン rshd やセキュアシェルデーモン sshd によって実行された場合など、標準入力がネットワーク接続に接続された 状態で実行されたかどうかを調べます。 この方法によって実行されていると bash が判断した場合、 ~/.bashrc が存在し、かつ読み込み可能であれば、 bash はコマンドをこのファイルから読み込んで実行します。

orz...
もちろん、sshのコマンドにゴリゴリと長い命令を書くことは普通ないので、
あまり気にすることでもないのかもしれませんが、
.bashrcには対話シェルで使う設定を書く」という金科玉条は脆くも崩れ去りました。
またこれは、「.bashrcに様々な設定を書いても、シェルスクリプト実行時にはそれらの影響を受けない」
という神話もまた失われた1ことを意味しています。

sshでコマンド実行時の回避策

対話シェルの起動ではないのに.bashrcが呼ばれるという悲劇をどうにか回避できないでしょうか?
大丈夫です、策はあります。
変数$-にはシェルのパラメータが格納されており、そこには対話シェルか否かを示すフラグも入っています。
実際に実行してみるとこんな感じです。

$-の中身の確認
[user-hoge@testhost ~]$ echo $-            #普通にecho
himBH
[user-hoge@testhost ~]$ ssh localhost 'echo $-' #ssh先でecho
bashrc 1
bashrc 2
hBc

今回注目すべきは「i」で、「interactive(対話的)」の頭文字です。
ということで、これで分岐させるように.bashrcを書いてあげます。

~/.bashrc
# 対話シェルでなかったら何も設定しない

echo "bashrc 1"
if [[ $- =~ i ]]; then            #対話シェルかどうか
    if [ -f /etc/bashrc ]; then
        . /etc/bashrc
    fi
    #ここに設定を書く
    echo "bashrc 2"
fi

標準の仕様からの逸脱が心配な人は、/etc/bashrcだけは読むというマイルドな変更もアリです。

~/.bashrc
# 対話シェルでなかったら/etc/bashrcを呼び出すだけ

echo "bashrc 1"
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

if [[ $- =~ i ]]; then            #対話シェルかどうか
    #ここに設定を書く
    echo "bashrc 2"
fi

上記を設定して、確認してみます。

[user-hoge@testhost ~]$ ssh localhsot 'echo $-'
bashrc 1
hBc

バッチリ bashrc 2 の部分は通っていません。
これで、sshでコマンドを実行させた時も.bashrcの影響を受けることはありません。

.bash_profile

もう結論出てた

上記の回避策をしたことで、.bashrcについての金科玉条
.bashrcには対話シェルで使う設定を書く 」の再建に成功しました。
では、残った.bash_profileには何を書くのでしょうか?

答えは非常に簡単です。
.bash_profileはデフォルトのままいじらない

だってここまでで、「.bash_profileは読むけど.bashrcは読まない」なんて
パターン一つもなかったじゃないですか。だからいいんですよ。全部.bashrcで。
.bash_profile.bashrcに設定を分けている人は、全て.bashrcに移管させましょう。
うん、それでOK。

でも理解したいかな

ここまでで、.bash_profileには一切手を触れないで良いという結論に達しましたが、
では.bash_profileとは一体何なんでしょう?
もう一度manを見返すと.bash_profileの説明に「ログインシェルが実行します」とありました。
この文章、「ログインシェル」がわからないと、まったく意味不明ですよね。
しかし、世の中に「ログインシェル」を説明しないで.bash_profileを解説する文章の多いこと…。

ということで、少し足を伸ばして「ログインシェル」について説明してみます。
ここがわかると、モヤモヤしていた部分がかなりスッキリするかな、と思います。
(私は以下のことがわかった時、目からウロコでした)

ログインシェル

ログインシェルはいつ動く?

.bash_profileは「ログインシェルが実行」するのですから、逆に言えば
「少なくとも.bash_profileが呼び出された瞬間にはログインシェルが動いている」ということです。
で、それは既に見たように、例えば、以下のようにsshで対話シェル接続したときです。

sshで対話シェル接続した時の例
[user-hoge@testhost ~]$ ssh localhsot
bash_profile 1
bashrc 1
bashrc 2
bash_profile 2

では次に、せっかくGNOME DesktopとしてインストールしたOSを使っているので、
そのGUI画面からターミナルを立ち上げて見ましょう。

image.png

.bash_profileは…、読まれてないっすね。。。
そう、 LinuxのGUI画面から端末を起動した場合、.bash_profileは読まれません
が・・・

image.png

.bash_profileの中で設定されるはずの設定が有効 になっています。1
(どちらのファイルにも書かれていないパスも設定されていますが、それは/etc/bashrcの仕業です)

・・・どうでしょう?.bash_profileの読まれるタイミング、
勘のいい人はそろそろ気づいたかもしれませんね。

GUIでの.bash_profileの役割

GUIの場合、ログイン画面からログインした瞬間、それがまさにログインシェルが動くタイミングです。
つまり、OSにログインしたタイミングで.bash_profileが読み込まれています。
そしてそこで読み込まれた 設定は、ターミナル環境以外の部分(=GUI等)にも反映 されます。

さっそく確認してみましょう。
.bash_profileに一行書き加えて、ログアウト後ログインします。

~/.bash_profile
echo "bash_profile 1"
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH
export LANG=C                          #言語設定を"C"に
echo "bash_profile 2"

image.png

.bash_profileが読み込まれて、(ターミナルとかまったく関係なく、)
GUIの画面にもユーザの言語設定 C が適用されているのがわかります。

結論として、.bash_profileというのは、対話シェルなどとは関係なく、
その ユーザのプロセス全体の基盤となる設定を書く 場所です。
なので、 .bash_profileに不用意な設定を書くことは、
ログイン環境全体に悪影響を及ぼす可能性がある
ということです。
だからこそ、 設定は.bashrcに。.bash_profileはそのままに。 が結論です。

気づいてみると、思ったよりキッパリと役割が違いました。

なぜ分かりにくかったのか

.bash_profileの正体がつかめたので、改めてsshでの対話シェル接続を考えます。
sshでの対話シェル接続の場合、OSへのログインと対話シェルの開始が必ず 同時に 起こっています。
同時に起こっているから、.bash_profileだけ、.bashrcだけという概念は顔を出しません。
だから、このログイン方法しかしないユーザにとって、2つのファイルはまったく同等なのです。

また、ここでは詳細は省きますが、GUIでターミナルを起動した時とは違って、
sshの対話シェル接続では.bash_profileの設定はすべてその対話シェルに適用されます。
このような場面では本当に.bashrcと書き分ける意味がなく、同列になってしまうのも混乱のもとです。2

ログインシェルを変えてみる

さて、最後に「ログインシェル」を理解するためにもう一つ検証をしてみましょう。
上で検証用に、.bash_profileに export LANG=Cを書きましたが、
その設定を残したまま、ログインシェルをbashではなくtcshにしてみます。

ログインシェルをtcshに変更
[user-hoge@localhost ~]$ chsh -s /bin/tcsh
Changing shell for user-hoge.
Password:
Shell changed.

これで一度ログアウトして、再度ログインしてみます。

image.png

予想通り、言語設定が日本語になりましたね。
もちろんこれは、 .bash_profileが呼ばれる代わりに、
tcshにおける.bash_profile的なファイル
(名前調べてない)が呼ばれているからです。

まとめ

だいぶ長くなってしまいました…。
ここまで読んでくれた方、ありがとうございます。
ここまで読み飛ばした方、よろしくおねがいします。

検証した挙動のまとめ

  • ログインシェルにbashを設定している場合、OSへの「ログイン」時に .bash_profile が読み込まれる。
    • 「ログイン」:GUIのシステムであれば、ログイン画面からログインした瞬間
      • GUI画面等、端末以外の部分にも.bash_profileに書かれた設定内容が反映される
      • ログイン後に端末を起動した時には.bashrcだけが呼び出される
        • .bash_profileは既にその外側で読み込まれているので、設定が引き継がれる1
    • 「ログイン」:sshの対話シェル接続であれば、接続成功した瞬間
      • 対話シェルが.bash_profileを呼ぶので、その設定内容が対話シェルに適用される。
      • .bash_profile.bashrcを呼ぶので、その対話シェルには.bashrcの内容も設定される
    • 対話シェル内などから新たにbashを立ち上げた場合は「ログイン」ではない。
      • .bash_profile.bashrcも読み込まれない
  • ssh testhost "<cmd>"した時には.bashrcが読まれてしまう1
    • 実行コマンドが.bashrcの影響を受けてしまうので、回避策を取る

.bash_profileと.bashrcに書くべき内容のまとめ

  • .bash_profile
    • 基本的に デフォルトのまま一切いじらない
    • 端末操作以外でも設定しておく必要がある環境変数のみ設定する
  • .bashrc
    • .bash_profileに書いた設定以外はすべてここに書く
    • ほとんどの人は、 設定は全部.bashrcに書けば良い

主に参考にさせて頂いたページ


  1. 正確には、設定ファイル内に書かれた環境変数の設定だけが有効になります。これについては、「シェル間の設定の引継ぎ」というテーマで後日別稿を書く予定です。 

  2. この部分の説明は、注釈1と同様、「シェル間の設定の引継ぎ」(執筆予定)の方を参照してください。 

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.