bashをログインシェルとしたとき、複数の設定ファイルを順番に読み込むことを知ったが、いまいち納得できなかった
なぜ複数あり、なぜその順番になっているのか?
結論:「誰が・どういう目的でbashを起動したのか」によって読み込む設定を変えるため
「全ユーザー共通の設定」「ユーザー個人の設定」「ログイン時だけの設定」「対話操作時だけの設定」を分離
読み込み順序は、
共通設定 → 個人設定 → より具体的な設定
という「上書きできる仕組み」になっている
そもそもbashとは
ユーザーがOSに命令を伝えるための「シェル(shell)」の一種
英語の shell(殻) という名前がわかりやすい。コンピュータと人間の間に入る
コンピューターの中心を、Linux ・Kernel と考えるとカーネルがCPU、メモリ、ディスク、ネットワークなどを管理している
┌─────────────────────┐
│ Linux │
│ Kernel │
└─────────────────────┘
人間が直接カーネルに 「このファイルを表示して!」 「このプロセスを終了して!」と命令しようとするととても面倒なことになる
そこで、その外側に人間が操作しやすいインターフェースを置くことができる
あなた
↓
┌───────┐
│ Bash │ ← Shell
└───────┘
↓
┌─────────────┐
│ Linux Kernel│
└─────────────┘
つまり、「人間の命令を受け取り、それをLinux上で実行するための通訳兼操作環境」のこと
bashには「種類」がある
A. ログインシェル
SSHで、ログインした場合に
ssh opc@サーバー
コマンドが実行されると
「ユーザーがLinuxにログインして、作業を始める」
というbashができる
SSH接続するたびに、そのログインセッション用のBashが起動される
B. 非ログインシェル
ログインした後、
bash
と入力した場合などに起動する
何かにログインしているわけではない
C. 対話型シェル
人間とリアルタイムに対話しながら入力を受け付けるシェル
$ ls
$ cd /var/log
$ pwd
こういうコマンドを入力すると応じたプログラムを起動してくれる
D. 非対話型シェル
人間がコマンドを入力しない形式のシェル
スクリプトを実行するときなどは対話型ではない
./backup.sh
このようなファイルのスクリプトを実行するときは非対話型シェルが起動している
上記の分類でいうと、bashの種類についてはログイン/非ログイン と 対話型/非対話型 という2つの軸で考えると整理しやすい
bash
│
┌───────┴─────────┐
ログイン 非ログイン
┌─────┴─────┐ ┌─────┴─────┐
対話型 非対話型 対話型 非対話型
└── SSHなど
状況に適した設定ファイルが必要
ひとつの設定ファイルにすべて記載すると、コンピュータのリソースを無駄にしてしまうため、使い分けが必要になる
設定ファイルの例
| ファイル | 主な役割 |
|---|---|
/etc/profile |
全ユーザー共通のログイン設定 |
~/.bash_profile |
そのユーザーのログイン設定 |
~/.bashrc |
そのユーザーの対話型bash設定 |
/etc/bashrc |
システム全体の対話型bash設定 |
例えば設定ファイルを
~/.bashrc
に絞り、下記の設定を入力する
export PATH=...
alias ll='ls -la'
echo "Welcome!"
この状態だと、
スクリプトを実行しただけなのにログイン用の処理が実行される
SSHログインしたときだけ必要な設定を、単なるbash起動でも実行してしまう
といった問題が起きる
そこでbashは、起動条件ごとに設定ファイルを分けることで、不要な初期化処理を適用しない構成にできる
「ログインシェル」の場合
Bashをログインシェルとして起動した場合、典型的には、
/etc/profile
↓
~/.bash_profile
↓
~/.bashrc
の順に読み込まれる
Bashのログインシェルはまず、
/etc/profile
を読み、その後、
~/.bash_profile
→ なければ ~/.bash_login
→ それもなければ ~/.profile
という探索をする
ログインシェルが読む順番(Oracle Linux 9の場合)
| ファイル | 主な役割 | |
|---|---|---|
| 1 | /etc/profile |
全ユーザー共通のログイン設定 |
| 2 | ~/.bash_profile |
そのユーザーのログイン設定 |
| 3 | ~/.bashrc |
そのユーザーの対話型bash設定 |
| 4 | /etc/bashrc |
システム全体の対話型bash設定 |
標準とはまた違う可能性があるらしいので、正確には下記を参照
参照:
https://www.gnu.org/software/bash/manual/bash.pdf?utm_source=chatgpt.com
なぜ /etc/profile が最初なのか
システム全体の設定を先に適用するため
例えばユーザー100人を管理している管理者が、
PATH=/usr/local/bin:/usr/bin:/bin
のような環境を全ユーザーの設定ファイルに書くのは面倒だから
/etc/profile
には共通設定を書く!
~/.bash_profileとは?
/etc/profileにあるユーザー共通の設定を上書きできる(イメージの)ファイル
さらに正確に表現するならユーザー固有の初期化処理のためのファイル
例えば管理者が、ユーザー共通として下記を設定したとする
PATH=/usr/local/bin:/usr/bin:/bin
一般ユーザー共通の設定は上記のままにして、~/.bash_profileに下記を設定すれば、自分だけ違う設定にできる
PATH=/home/admin/bin:/usr/local/bin:/usr/bin:/bin
/etc/profile
で全員共通の初期設定をして、
その後、
~/.bash_profile
で個人設定を必要な部分だけ上書きしたような状態にできる
後から実行された設定が、現在の設定を変更できる
例えば、
/etc/profile :
EDITOR=vi
~/.bash_profile :
EDITOR=vim
の場合、最終的な環境は
EDITOR=vim
になる
| ファイル | 主な役割 | パラメータ | |
|---|---|---|---|
| 1 | /etc/profile |
全ユーザー共通のログイン設定 | EDITOR=vi |
| 2 | ~/.bash_profile |
そのユーザーのログイン設定 | EDITOR=vim |
| 3 | ~/.bashrc |
そのユーザーの対話型bash設定 | 書けるけど書かない |
| 4 | /etc/bashrc |
システム全体の対話型bash設定 | 書けるけど書かない |
/etc/profile
↓
EDITOR=vi
~/.bash_profile
↓
EDITOR=vim
最終結果
↓
EDITOR=vim
.bashrc について
.bashrc は通常、 対話型bashのための設定を書く場所
「人間がコマンドを打つとき便利にする設定」
例:
alias ll='ls -lah'
alias ..='cd ..'
PS1='\u@\h:\w\$ '
Red Hat系の環境では ~/.bash_profile の中に下記のように記載し、自動で続けて読み込むように設定していることが多い
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
読み込む順番
| ファイル | 主な役割 | |
|---|---|---|
| 1 | /etc/profile |
全ユーザー共通のログイン設定 |
| 2 | ~/.bash_profile |
そのユーザーのログイン設定 |
| 3 | ~/.bashrc |
そのユーザーの対話型bash設定 |
| 4 | /etc/bashrc |
システム全体の対話型bash設定 |
整理すると
| 分類 | システム全体 | そのユーザーだけ | |
|---|---|---|---|
| /etc/ | ~/ | ||
| ログイン関係 | profile | /etc/profile |
~/.bash_profile |
| 対話操作関係 | bashrc | /etc/bashrc |
~/.bashrc |
Unix/Linuxの設計思想
Linuxの設定はログイン時の環境設定に限らず 共通設定をまず読み、その後で個人が必要な部分を変更する
また、個別に必要な「上書き」をするために「順番」を決めている
各設定ファイルについて暗記をするより、下記の原則がどう適用されるかを覚えておく方が便利
-bashは、自分がどういう状況で起動されたかによって読む設定を変える
-大きな範囲⇔小さな範囲について設定されたファイルを読み込む
-システム全体⇔各機能
-全ユーザー共通⇔個別ユーザー
ログインシェルの場合:
/etc/profile :全ユーザー共通のログイン環境
↓
~/.bash_profile :ユーザー個別のログイン環境
↓
~/.bashrc :ユーザー個別の対話操作用の設定
課題
先述の設計思想によると、~/.bash_profileよりも先に/etc/bashrcの方が読まれないといけないはず
-大きな範囲→小さな範囲の順に設定ファイルを読み込む
-全ユーザー共通→個別ユーザー
ではなぜ、/etc/bashrcが例外になっているのか?
/etc/profile はBash専用のファイルではなく、伝統的にログイン時の共通環境設定に使われるファイルで、シェルによって読み込まれ方が異なる
などの理由がありそうだが、はっきりしたことはわからずこの記事では解決できなかった。今回の時点では例外として覚えておく
bash
│
┌─────────┴─────────┐
│ │
login shell non-login shell
│ │
/etc/profile │
│ │
~/.bash_profile │
│ │
~/.bashrc ←─────────────┘
│
▼
対話環境完成
付録
下記コマンドで現環境を調査した
詳しく読み込んで理解したいが、それも後に譲ることにする
cat /etc/os-release
echo "$SHELL"
ps -p $$ -o pid,ppid,args
cat /etc/profile
cat ~/.bash_profile
cat ~/.bashrc
cat /etc/bashrc
[opc@ホスト名 ~]$ cat /etc/os-release
NAME="Oracle Linux Server"
VERSION="9.7"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="9.7"
PLATFORM_ID="platform:el9"
PRETTY_NAME="Oracle Linux Server 9.7"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:9:7:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://github.com/oracle/oracle-linux"
ORACLE_BUGZILLA_PRODUCT="Oracle Linux 9"
ORACLE_BUGZILLA_PRODUCT_VERSION=9.7
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=9.7
[opc@ホスト名 ~]$
[opc@ホスト名 ~]$ echo "$SHELL"
/bin/bash
[opc@ホスト名 ~]$
[opc@ホスト名 ~]$ ps -p $$ -o pid,ppid,args
PID PPID COMMAND
46629 46628 -bash
[opc@ホスト名 ~]$
[opc@ホスト名 ~]$ cat /etc/profile
# /etc/profile
# System wide environment and startup programs, for login setup
# Functions and aliases go in /etc/bashrc
# It's NOT a good idea to change this file unless you know what you
# are doing. It's much better to create a custom.sh shell script in
# /etc/profile.d/ to make custom changes to your environment, as this
# will prevent the need for merging in future updates.
pathmunge () {
case ":${PATH}:" in
*:"$1":*)
;;
*)
if [ "$2" = "after" ] ; then
PATH=$PATH:$1
else
PATH=$1:$PATH
fi
esac
}
if [ -x /usr/bin/id ]; then
if [ -z "$EUID" ]; then
# ksh workaround
EUID=`/usr/bin/id -u`
UID=`/usr/bin/id -ru`
fi
USER="`/usr/bin/id -un`"
LOGNAME=$USER
MAIL="/var/spool/mail/$USER"
fi
# Path manipulation
if [ "$EUID" = "0" ]; then
pathmunge /usr/sbin
pathmunge /usr/local/sbin
else
pathmunge /usr/local/sbin after
pathmunge /usr/sbin after
fi
HOSTNAME=$(/usr/bin/hostnamectl --transient 2>/dev/null) || \
HOSTNAME=$(/usr/bin/hostname 2>/dev/null) || \
HOSTNAME=$(/usr/bin/uname -n)
HISTSIZE=1000
if [ "$HISTCONTROL" = "ignorespace" ] ; then
export HISTCONTROL=ignoreboth
else
export HISTCONTROL=ignoredups
fi
export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL
for i in /etc/profile.d/*.sh /etc/profile.d/sh.local ; do
if [ -r "$i" ]; then
if [ "${-#*i}" != "$-" ]; then
. "$i"
else
. "$i" >/dev/null
fi
fi
done
unset i
unset -f pathmunge
if [ -n "${BASH_VERSION-}" ] ; then
if [ -f /etc/bashrc ] ; then
# Bash login shells run only /etc/profile
# Bash non-login shells run only /etc/bashrc
# Check for double sourcing is done in /etc/bashrc.
. /etc/bashrc
fi
fi
[opc@ホスト名 ~]$
[opc@ホスト名 ~]$ cat ~/.bash_profile
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
[opc@ホスト名 ~]$
[opc@ホスト名 ~]$ cat ~/.bashrc
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# User specific environment
if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]]
then
PATH="$HOME/.local/bin:$HOME/bin:$PATH"
fi
export PATH
# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=
# User specific aliases and functions
if [ -d ~/.bashrc.d ]; then
for rc in ~/.bashrc.d/*; do
if [ -f "$rc" ]; then
. "$rc"
fi
done
fi
unset rc
[opc@ホスト名 ~]$
[opc@ホスト名 ~]$ cat /etc/bashrc
# /etc/bashrc
# System wide functions and aliases
# Environment stuff goes in /etc/profile
# It's NOT a good idea to change this file unless you know what you
# are doing. It's much better to create a custom.sh shell script in
# /etc/profile.d/ to make custom changes to your environment, as this
# will prevent the need for merging in future updates.
# Prevent doublesourcing
if [ -z "$BASHRCSOURCED" ]; then
BASHRCSOURCED="Y"
# are we an interactive shell?
if [ "$PS1" ]; then
if [ -z "$PROMPT_COMMAND" ]; then
case $TERM in
xterm*|vte*)
if [ -e /etc/sysconfig/bash-prompt-xterm ]; then
PROMPT_COMMAND=/etc/sysconfig/bash-prompt-xterm
else
PROMPT_COMMAND='printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"'
fi
;;
screen*)
if [ -e /etc/sysconfig/bash-prompt-screen ]; then
PROMPT_COMMAND=/etc/sysconfig/bash-prompt-screen
else
PROMPT_COMMAND='printf "\033k%s@%s:%s\033\\" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"'
fi
;;
*)
[ -e /etc/sysconfig/bash-prompt-default ] && PROMPT_COMMAND=/etc/sysconfig/bash-prompt-default
;;
esac
fi
# Turn on parallel history
shopt -s histappend
history -a
# Turn on checkwinsize
shopt -s checkwinsize
[ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@\h \W]\\$ "
# You might want to have e.g. tty in prompt (e.g. more virtual machines)
# and console windows
# If you want to do so, just add e.g.
# if [ "$PS1" ]; then
# PS1="[\u@\h:\l \W]\\$ "
# fi
# to your custom modification shell script in /etc/profile.d/ directory
fi
if ! shopt -q login_shell ; then # We're not a login shell
# Need to redefine pathmunge, it gets undefined at the end of /etc/profile
pathmunge () {
case ":${PATH}:" in
*:"$1":*)
;;
*)
if [ "$2" = "after" ] ; then
PATH=$PATH:$1
else
PATH=$1:$PATH
fi
esac
}
# Set default umask for non-login shell only if it is set to 0
[ `umask` -eq 0 ] && umask 022
SHELL=/bin/bash
# Only display echos from profile.d scripts if we are no login shell
# and interactive - otherwise just process them to set envvars
for i in /etc/profile.d/*.sh; do
if [ -r "$i" ]; then
if [ "$PS1" ]; then
. "$i"
else
. "$i" >/dev/null
fi
fi
done
unset i
unset -f pathmunge
fi
fi
# vim:ts=4:sw=4
[opc@ホスト名 ~]$