この記事の内容
.bash_profile
と .bashrc
の使い分けの説明はいろいろあるんですが、
どうにも腑に落ちなかったので、私なりに調べてみました。
で、自分的にはこれで必要十分な理解ができたと思ったので投稿。
とりあえずここまで理解すれば、後はその都度対処していけるかなー、と。
2018/03/20追記 :もともと合わせて1つの記事にする予定だった
「Linuxでシェル間の設定の引継ぎを蛇足感満載で確認した」という記事を投稿しました!
こちらと合わせて読んでいただけると嬉しいです。
スーパー忙しい人のための結論
設定はすべて.bashrc
に書く
これでいいんやで。
動作確認の環境
- Windows10のHyper-V上で動いているCentOS7で動作を確認
- CentOS7はインストール直後の状態で試しているので設定ファイル等は書き換えていない
- OSインストールのグループ(最小限とかから選ぶやつね)は「GNOME Desktop」を選択
- Ubuntuなどのディストリビューションでは動作が異なるかもだけど、今回は言及しない
- ここの説明を理解すれば、他のディストリビューションにもすぐに応用できると思う
基本動作のおさらい
初期ファイルの内容
まずはCentOS7インストール直後の.bash_profile
と.bashrc
の中身を見てみます。
まずは.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
。
echo "bashrc 1" #検証のために追加
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
echo "bashrc 2" #検証ために追加
こちらは共通設定ファイルである /etc/bashrc
を読み込むだけです。
/etc/bashrc
はrootが所有するファイルで、全ユーザ共通の設定が書かれています。
このファイルに関しては、ちゃんと呼ぶ設定になっている必要はありますが、
中を見る必要はないですから、この記事では基本的に一切触れません。
ssh接続による起動
次に、実際にbashを起動するために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を起動する場面をいくつか試してみます。
[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存在意義の破綻!?1
sshでのコマンド実行
.bashrc
は対話シェルの起動時に読み込まれるものなので、
対話シェルだけで使用するような設定を書くっぽい、というのがここまでの話でした。
しかし、何故かそこに反旗を翻す輩が…。
[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
に様々な設定を書いても、シェルスクリプト実行時にはそれらの影響を受けない2」
という神話もまた失われたことを意味しています。
sshでコマンド実行時の回避策
対話シェルの起動ではないのに.bashrc
が呼ばれるという悲劇をどうにか回避できないでしょうか?
大丈夫です、策はあります。
変数$-
にはシェルのパラメータが格納されており、そこには対話シェルか否かを示すフラグも入っています。
実際に実行してみるとこんな感じです。
[user-hoge@testhost ~]$ echo $- #普通にecho
himBH
[user-hoge@testhost ~]$ ssh localhost 'echo $-' #ssh先でecho
bashrc 1
bashrc 2
hBc
今回注目すべきは「i」で、「interactive(対話的)」の頭文字です。
ということで、これで分岐させるように.bashrc
を書いてあげます。
2018/03/17修正 :以前は、/etc/bashrc
も読まないような分岐も掲示していましたが、削除しました。
管理者は、sshの非対話シェル接続でも/etc/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で対話シェル接続したときです。
[user-hoge@testhost ~]$ ssh localhsot
bash_profile 1
bashrc 1
bashrc 2
bash_profile 2
では次に、せっかくGNOME DesktopとしてインストールしたOSを使っているので、
そのGUI画面からターミナルを立ち上げて見ましょう。
.bash_profile
は…、読まれてないっすね。。。
そう、 LinuxのGUI画面から端末を起動した場合、.bash_profile
は読まれません 。
が・・・
.bash_profile
の中で設定されるはずの設定が有効 になっています。3
(どちらのファイルにも書かれていないパスも設定されていますが、それは/etc/bashrc
の仕業です)
・・・どうでしょう?.bash_profile
の読まれるタイミング、
勘のいい人はそろそろ気づいたかもしれませんね。
GUIでの.bash_profileの役割
GUIの場合、ログイン画面からログインした瞬間、それがまさにログインシェルが動くタイミングです。
つまり、OSにログインしたタイミングで.bash_profile
が読み込まれています。
そしてそこで読み込まれた 設定は、ターミナル環境以外の部分(=GUI等)にも反映 されます。
さっそく確認してみましょう。
.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"
.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
と書き分ける意味がなく、同列になってしまうのも混乱のもとです。
(GUIでは.bash_profile
に書いたもののうち、環境変数しか適用されません)
ログインシェルを変えてみる
さて、最後に「ログインシェル」を理解するためにもう一つ検証をしてみましょう。
上で検証用に、.bash_profileに export LANG=C
を書きましたが、
その設定を残したまま、ログインシェルをbashではなくtcshにしてみます。
[user-hoge@localhost ~]$ chsh -s /bin/tcsh
Changing shell for user-hoge.
Password:
Shell changed.
これで一度ログアウトして、再度ログインしてみます。
予想通り、言語設定が日本語になりましたね。
もちろんこれは、 .bash_profile
が呼ばれる代わりに、
tcshにおける.bash_profile
的なファイル(名前調べてない)が呼ばれているからです。
まとめ
だいぶ長くなってしまいました…。
ここまで読んでくれた方、ありがとうございます。
ここまで読み飛ばした方、よろしくおねがいします。
検証した挙動のまとめ
- ログインシェルにbashを設定している場合、OSへの「ログイン」時に
.bash_profile
が読み込まれる。- 「ログイン」:GUIのシステムであれば、ログイン画面からログインした瞬間
- GUI画面等、端末以外の部分にも
.bash_profile
に書かれた設定内容が反映される - ログイン後に端末を起動した時には
.bashrc
だけが呼び出される-
.bash_profile
は既にその外側で読み込まれているので、設定が引き継がれる3
-
- GUI画面等、端末以外の部分にも
- 「ログイン」:sshの対話シェル接続であれば、接続成功した瞬間
- 対話シェルが
.bash_profile
を呼ぶので、その設定内容が対話シェルに適用される。 -
.bash_profile
が.bashrc
を呼ぶので、その対話シェルには.bashrc
の内容も設定される
- 対話シェルが
- 対話シェル内などから新たにbashを立ち上げた場合は「ログイン」ではない。
-
.bash_profile
も.bashrc
も読み込まれない
-
- 「ログイン」:GUIのシステムであれば、ログイン画面からログインした瞬間
-
ssh testhost "<cmd>"
した時には.bashrc
が読まれてしまう- 実行コマンドが
.bashrc
の影響を受けてしまうので、回避策を取る
- 実行コマンドが
.bash_profileと.bashrcに書くべき内容のまとめ
-
.bash_profile
- 基本的に デフォルトのまま一切いじらない
- 端末操作以外でも設定しておく必要がある環境変数のみ設定する
-
.bashrc
-
.bash_profile
に書いた設定以外はすべてここに書く - ほとんどの人は、 設定は全部
.bashrc
に書けば良い
-