ykrods note

.bashrc, .bash_profile と bash シェル (Ubuntu 18.04)

はじめに

.bashrc の設定なんか読み込まれないなぁというよくあるアレに陥ったので調べてまとめた。

警告

Ubuntu 18.04 で確認した内容をまとめています。ディトリビューションによって差異があるらしいのでご注意ください。

man bash

# man bash より引用

When an interactive shell that is not a login shell is started, bash reads and executes commands
from  /etc/bash.bashrc  and ~/.bashrc, if these files exist.

# (訳) ログインシェルでないインタラクティブシェルが始まった時、 /etc/bash.bashrc や ~/.bashrc が存在すれば、
#     それらを読み込んで実行する

When  bash  is  invoked  as  an  interactive login shell, or as a non-interactive shell with the
--login option, it first reads and executes commands from the file /etc/profile,  if  that  file
exists.   After  reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile,
in that order, and reads and executes commands from the first one that exists and  is  readable.

# (訳) インタラクティブログインシェル、あるいは --login オプション付きノンインタラクティブシェル
#      として bash が呼び出された場合、 ~/.bash_profile , ~/.bash_login, ~/.profile の順で
#      最初の存在するファイルを読み込んで実行する

とりあえず上記を表にまとめるとこうなる。

種別

読み込むファイル

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

  • /etc/profile

  • ~/.bash_profile, ~/.bash_login, ~/.profile から一つ

インタラクティブ(ログインじゃない)シェル

  • /etc/bash.bashrc

  • ~/.bashrc

ノンインタラクティブシェル+ login オプション

  • /etc/profile

  • ~/.bash_profile, ~/.bash_login, ~/.profile から一つ

ノンインタラクティブシェル

何も読み込まない?

また ssh については別の段落にこう書かれている

# man bash より引用, bash のバージョンは 4.4.20

Bash  attempts  to determine when it is being run with its standard input connected to a network
connection, as when executed by the remote shell daemon, usually rshd, or the secure shell  dae‐
mon  sshd.   If  bash determines it is being run in this fashion, it reads and executes commands
from ~/.bashrc and ~/.bashrc, if these files exist and are readable.

# (意訳)
# bash は(具体的には)通常 rshd あるいは リモートシェルデーモン sshd によって実行された場合の様な、
# 標準出力をネットワーク接続に接続した状態で実行しているかどうかの判断を試みる。
# bash がこのやり方で実行されていると判断した場合、 ~/.bashrc が存在すればそれを読み込む。

うむ、わからん。ということで各用語の整理と動作確認を行う。

インタラクティブログインシェル (あるいはログインシェル)

ログインしたときに起動するシェル。通常ログインしたら対話シェルが始まるので、単にログインシェルともいう。

起動方法

  • ログインする

  • $ su - [user] ( - は -l, --login の省略)

  • $ bash -l

  • $ sudo -i [-u user]

インタラクティブシェル

対話シェル。ログインシェルと対比して使われる場合はインタラクティブ(ログインじゃない)シェルの意味で使われる。厳密な定義は man bash を参照。

意識的に bash でインタラクティブシェルを起動することはあまりない様に思うが、例えば screen コマンドなどでは中でインタラクティブシェルが起動する。

起動方法

  • $ su [user] ( - 無し)

  • $ bash

  • $ sudo -s [-u user]

ノンインタラクティブシェル

非対話シェル。

起動方法

  • $ bash -c command_string

  • $ sudo -s [-u user] command

ノンインタラクティブシェル + ログインオプション

非対話だがログイン時の設定( *profile )を読み込みたい場合に使う。

起動方法

  • $ bash -lc command_string

  • $ sudo -i [-u user] command

初期設定

Ubuntu18.04 の設定ファイルがどの様になっているかを確認する。

.bashrc

  • 以下の記述によりノンインタラクティブな場合は何もしない様になっている

    # If not running interactively, don't do anything
    case $- in
        *i*) ;;
          *) return;;
    esac
    
    • ( $ bash -c 'echo $-' を実行すると hBc が帰ってくる。i を含んでいないので return される

  • その他の設定項目

    • ll, la などのエイリアス

    • 色関連

    • bash の入力補完

  • => 内容的に、.bashrc は対話シェルで適用されることが想定されていると判断できる。

.profile

  • ~/.bashrc の読み込み

    • man にある様にログインシェルは .bashrc を読み込まないので、.profile の中で . "$HOME/.bashrc" している

  • パス設定

.bash_profile

  • デフォルトで存在しない

ちなみに

これ系の記事では .bash_profile について説明しているものが大半だが、少なくとも Ubuntu 18.04 では .bash_profile を設置することによりデフォルトの .profile が読み込まれなくなる(.bash_profile の方が優先度が高い)ことに注意が必要

動作検証

su, sudo, bash コマンドでの .bashrc, .profile の読み込みの挙動を確認する。

準備

$ sudo useradd -m -s /bin/bash hogeo
$ sudo su - hogeo
$ echo 'export PATH="$HOME/.bin1:$PATH"' >> .bashrc
$ echo 'echo ".bashrc loaded"' >> .bashrc
$ echo 'export PATH="$HOME/.bin2:$PATH"' >> .profile
$ echo 'echo ".profile loaded"' >> .profile
  • .bashrc, .profile それぞれの読み込み時に echo する

  • 検証のため、それぞれのファイルで PATH に $HOME/{.bin1, .bin2} を追加する(値に意味はない)

  • デフォルトの設定に変更は加えない( .profile から .bashrc を読み込む)

su -

$ sudo su - hogeo
.bashrc loaded
.profile loaded
$ env | grep PATH
PATH=/home/hogeo/.bin2:/home/hogeo/.bin1:/usr/local/sbin: ..()

両方読み込まれる

su

$ sudo su hogeo
.bashrc loaded
$ env | grep PATH
PATH=/home/hogeo/.bin1:/usr/local/sbin: ..()

.bashrc のみ読み込まれる

bash

$ sudo su - hogeo
.bashrc loaded
.profile loaded
$ bash
.bashrc loaded
$ env | grep PATH
PATH=/home/hogeo/.bin1:/home/hogeo/.bin2:/home/hogeo/.bin1:/usr/local/sbin: ..()

su -bash で .bashrc が二回読み込まれ、 .bin1 のパスが二重になっている(実害はないが気持ちが悪い)

bash -l

$ sudo su - hogeo
.bashrc loaded
.profile loaded
$ bash -l
.bashrc loaded
.profile loaded
$ env | grep PATH
PATH=/home/hogeo/.bin2:/home/hogeo/.bin1:/home/hogeo/.bin2:/home/hogeo/.bin1:/usr/local/sbin: ..()

su -bash -l でそれぞれ .profile, .bashrc を読み込んでいる。

この辺はまぁそもそも su - しているのだから bash -l する必要ないよね、ということでいいのだろうか?

( 上記の様なパス追加なら重複するだけだが、ログイン時に二重に実行されると困る様な処理を挟む場合は何かしら対応する必要がある。

bash -c command_string

$ sudo su - hogeo
.bashrc loaded
.profile loaded
$ bash -c "env | grep PATH"
PATH=/home/hogeo/.bin2:/home/hogeo/.bin1:/usr/local/sbin: ..()

非対話シェルなので何も読み込まない

bash -lc command_string

$ sudo su - hogeo
.bashrc loaded
.profile loaded
$ bash -lc "env | grep PATH"
.profile loaded
PATH=/home/hogeo/.bin2:/home/hogeo/.bin2:/home/hogeo/.bin1:/usr/local/sbin: ..()

-l オプションにより .profile が読み込まれるが、非対話シェルなので .bashrc が読み込まない(中断される)

sudo -s [-u user] command

$ sudo -s -u hogeo env | grep PATH
PATH=/usr/local/sbin

非対話シェルなので何も読み込まれない

  • (オプションなしの sudo [-u user] command の場合はそもそもシェルを起動しない(ハズ))

sudo -i [-u user] command

$ sudo -i -u hogeo env | grep PATH
# .profile loaded
PATH=/home/hogeo/.bin2:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

-i オプションにより .profile が読み込まれるが、非対話シェルなので .bashrc が読み込まない(中断される)

sudo [-u user] bash -c command_string

$ sudo -u hogeo bash -c 'env | grep PATH'
SUDO_COMMAND=/bin/bash -c env | grep PATH
PATH=/usr/local/sbin: ..()

非対話シェルなので何も読み込まれない

sudo [-u user] bash -lc command_string

sudo -u hogeo bash -lc 'env | grep PATH'
SUDO_COMMAND=/bin/bash -lc env | grep PATH
PATH=/usr/local/sbin: ..()

sudo -i -u と違い、 .profile が読み込まれない

  • これは sudo のデフォルトの挙動では $HOME が sudo の実行ユーザのものから切り替わらないため

    • ( sudo の実行ユーザの $HOME/.profile が読み込まれる

    • ( sudo -u hogeo bash -lc 'echo $HOME' すると変わっていないのが確認できる

sudo に -H (--set-home) オプションを加えると $HOME が切り替わった上でコマンドが実行される

$ sudo -H -u hogeo bash -lc 'env | grep PATH'
.profile loaded
SUDO_COMMAND=/bin/bash -lc env | grep PATH
PATH=/home/hogeo/.bin2:/usr/local/sbin: ..()

ちなみに

余談になるが、ansible の become ディレクティブではデフォルトで sudo が使われ、デフォルトの become_flags-H が入っているので何もしなくても become_user で指定したユーザのホームに切り替わっている

ssh [user@]host

# ( vagrant で検証
$ ssh -p 2222 hogeo@127.0.0.1
.bashrc loaded
.profile loaded
$ env | grep PATH
PATH=/home/hogeo/.bin2:/home/hogeo/.bin1:/usr/local/sbin: ...()

ログインなので .profile が読み込まれる

# man bash の説明通りなら (英語を何かしら読み違えてない限り) この場合でも .bashrc を読み込むのではないかとも思えるが、ソースコードでは明確に非対話シェルで実行された場合に限定した上で ssh の場合に .bashrc を読み込む様になっているので、そういう仕様なのだろう ( shell.c:run_startup_files() )

ssh [user@]host command

$ ssh -p 2222 hogeo@127.0.0.1 env | grep PATH
PATH=/usr/local/sbin: ..()

man bash の通り .bashrc は読み込まれているが、非対話シェルなので中断される

# bash -lc 利用
$ ssh -p 2222 hogeo@127.0.0.1 "bash -lc 'env | grep PATH'"
.profile loaded
PATH=/home/hogeo/.bin2:/usr/local/sbin: ..()

コマンド指定の ssh で .profile が読み込みたい場合は bash -lc を使う。( 他にもやり方はあるが )

まとめ

デフォルトの設定に従う場合、以下の様にまとめられる。

種別

起動方法

読み込まれるファイル

ログインシェル

  • ログイン

$ su - [user]
$ sudo -i [-u user]
$ bash -l
$ ssh [user@]hostname

.profile, .bashrc

インタラクティブシェル

$ su [user]
$ sudo -s [-u user]
$ bash
$ screen

.bashrc

非対話シェル

$ bash -c command_string
$ sudo -s [-u user] command
$ ssh [user@]hostname command

なし

非対話シェル+ログインオプション

$ bash -lc command_string
$ sudo -i [-u user] command
$ sudo -H [-u user] \
    bash -lc command_string
$ ssh [user@]hostname \
    "bash -lc 'command_string'"

.profile

# (念のため注記) コマンド指定ありの ssh では仕様上 .bashrc は読み込まれるが、.bashrc のデフォルトの記述により読み込み中断されるため、 実質的に 読み込まれない扱いにしている。

.bashrc と .profile の使い分けとしては(これもデフォルト設定に合わせるなら)以下

対話のための設定

.bashrc

それ以外

.profile

注意点

  • .profile (または .bash_profile) に書いても複数回呼ばれるときは呼ばれる

他の方針

これが正解というものはないと思うので、結局のところちゃんと理解した上でやりたいことに合ったポリシーで管理しようという話になるのではないかと思う。ということで少し他の方針についても触れる。

  • 全部 .bashrc に書く

    • 常に対話シェルを起動してからコマンドを叩く様な運用方針の場合

    • デフォルトの挙動を変えて非対話シェル + ログインオプションでも .bashrc を読み込ませる様に変更する場合

    • 個人的には、あえてデフォルトの挙動を変えてまでやるメリットは思い当たらないが、「常に対話シェルを起動する運用」は有り得なくもない?

  • .bash_profile を使う

    • 不意に .bash_profile が置かれたことにより .profile が読み込まれなくなる可能性があるので、最初から .bash_profile を使った方が安心というのはあるかもしれない。

    • 個人的な感覚では、正しく .profile の内容 ( 主に .bashrc の読み込み ) を反映させた上で .bash_profile を配置し、不要な .profile は消す、というところまでやればこれでも良いと思う。

    • デフォルトで .bash_profile が存在する OS では当然 .bash_profile を使えば良いと思う

参考

bash bash

.bashrc, .bash_profile と bash シェル (Ubuntu 18.04) — ykrods note
https://www.ykrods.net/posts/2021/06/12/bash-files/

Comments