- はじめに
- bash の初期化ファイル
- ログインシェルとは
- インタラクティブ(対話的)シェルとは
- ログインシェルとインタラクティブシェルの分類
- 各種環境における初期化ファイル
- 各環境におけるデフォルトの初期化ファイル
- 単一ファイル管理(~/.bashrc)
- 標準的管理(環境変数は~/.bash_profile)
- 厳格管理(~/.profile 利用)
- まとめ
はじめに
本記事では bash の初期化ファイルと、それにまつわる運用方法について説明します。
シェル(bash)の初期化ファイルには .profile
.bashrc
, .bash_profile
などがありますが、どこに何を定義すべきかについては色々な意見が散見され、結果ぼんやりしてしまうことがあります。
色々な意見が散見されるのは、唯一の正解などといったものは無いからなのですが、ここでは改めてシェルの初期化ファイルについて整理し、管理方針についての判断材料を提供したいと思います。
bash の初期化ファイル
ディストリビューションなどによりインポートするファイルが異なったり、オプションにより読み込むファイルを変更できたりしますが、bash の起動時には以下のような判定で読み込む初期化ファイルが変化します。
青塗りの四角が読み込む設定ファイルを表しています。
大きくは、ログインシェルかそうではないか、インタラクティブかそうではないか、で読み込むファイルが別れます。
ログインシェルの場合、/etc/profile
というシステムワイドの設定ファイルを読み、続いてユーザ設定ファイル ~/.bash_profile
, ~/.bash_login
, ~/.profile
の順番で最初に見つかったものだけを読みます。
よくあるのが、~/.profile
を使っていたけど、~/.bash_profile
を作成したら設定が読み込まれなくなった などは、このようなファイルの検索順序が影響するためです。
ログインシェルではない場合は ~/.bashrc
ファイルを読みます。
非インタラクティブの場合は $BASH_ENV
に指定されたファイルを初期化ファイルとして読み込みます。
図では省略していますが、ログインシェルの場合でも非インタラクティブの場合は、環境変数 $BASH_ENV
に設定されたファイルを初期化ファイルとして読みます。
$BASH_ENV
を読むケースは、主にシェルスクリプトを実行する場合に該当します。
ケースに応じて読み込むファイルが変わることがわかったところで、分岐の判定にある、ログインシェル と インタラクティブシェル とは何かについて見ていきましょう。
ログインシェルとは
ログインシェルとはデスクトップ画面や仮想コンソールでログインしたときに起動されるシェルを指します。
bash の man page から抜粋すると以下のように説明されています。
A login shell is one whose first character of argument zero is a -, or one started with the --login option.
ログインシェル(login shell)とは、0 番目の引き数の最初の文字が - であるシェル、または --login オプション付きで起動されたシェルのことです。
ここでの 0 番目の引数とは、bash に渡されたコマンドライン引数の最初の要素を意味します。
通常この引数にはプログラム起動時のコマンド名が入ります。
-bash
というコマンド名で起動した場合、最初の文字が -
なのでログインシェルとして起動することになります。
Linux におけるログインコマンドのソースファイル(login.c)は以下のようになっています。
tbuf[0] = '-'; xstrncpy(tbuf + 1, ((p = strrchr(pwd->pw_shell, '/')) ? p + 1 : pwd->pw_shell), sizeof(tbuf) - 1); // "-bash" childArgv[childArgc++] = pwd->pw_shell; // "/bin/bash" childArgv[childArgc++] = xstrdup(tbuf); // "-bash" childArgv[childArgc++] = NULL; execvp(childArgv[0], childArgv + 1);
上記ソースの2行目にある pwd
は passwd
構造体で、/etc/passwd
のエントリを参照しています。
この passwd ファイルは以下のような構成になっています。
root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin ...
1行に、ユーザ名:パスワード:ユーザID:グループID:コメント欄:ホームディレクトリ:ログインシェル が定義され、このエントリからログインシェルとして使うシェルを取得しています。
login.c のソースに戻り、最終行 execvp()
の第一引数 childArgv[0]
には /bin/bash
のようなコマンドのパス、第二引数の配列の最初の要素にはコマンド名として -bash
のように指定されることが分かります。
これにより先頭文字が -
となり、bash がログインシェルとして起動することになります。
具体的には以下のようなケースでログインシェルが起動します。
- ログインプロンプトよりシステムにログイン
bash --login
のように--login
オプションを明示して bash を起動su - <user>
のように-
(-l
または--login
)オプションを指定した場合、su コマンドがシェル名の前に-
を付加ssh user@hostname
などでSSHログイン
システムへログイン、再ログインするような、ユーザセッションの開始時に起動するシェルがログインシェルになります。
インタラクティブ(対話的)シェルとは
bash の man page から抜粋すると以下のように説明されています。
An interactive shell is one started without non-option arguments and without the -c option whose standard input and error are both con-nected to terminals (as determined by isatty(3)), or one started with the -i option. PS1 is set and $- includes i if bash is interactive, allowing a shell script or a startup file to test this state.
対話的なシェルとは、 オプションでない引き数がなく、 標準入力と標準エラー出力がいずれも端末に接続されていて (これは isatty(3) で調べられます)、 -c オプションが指定されていない状態で起動されたシェル、または -i オプション付きで起動されたシェルのことです。 bash が対話的に動作している場合には、 PS1 が設定され、 $- に i が含まれます。 これを利用すると、対話的動作の状態であるかどうかを、 シェルスクリプトや起動ファイルの内部で調べられます。
ざっくりまとめると、以下の状態で起動したシェルがインタラクティブシェル(対話的)となります。
- 標準入力と標準エラー出力が端末に接続されている
-c
オプションが指定されていない- オプション以外の引数が指定されていない
または
-i
オプションで強制的にインタラクティブ扱いになっている
出力は端末画面に表示されて、対話的に入力を受け付け、シェルスクリプトを実行するために起動されたシェルではない 場合にインタラクティブシェルとなります。
具体的には以下のケースでインタラクティブシェルになります。
- 仮想コンソールや ssh によるログイン
su [user]
のように-
無しで substitute user した場合bash
としてシェルを起動bash -i
でシェルスクリプトを実行
インタラクティブ(対話的)ではない場合には、非インタラクティブ(非対話的)シェルとなります。
bash の man page では、非インタラクティブ(非対話的) な場合の起動は以下のように説明されています。
(例えばシェルスクリプトを実行するために) 非対話的に起動されると、 bash は環境変数 BASH_ENV を調べ、この変数が定義されていればその値を展開し、 得られた値をファイル名とみなして、 そこからコマンドの読み込みと実行を行います。 つまり bash は以下のコマンドが実行されたのと同じように動作します:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
シェルスクリプトの実行や、bash -c <command>
のようにコマンドを実行した場合は 非インタラクティブ となります。
デスクトップ画面にログインしたときや、シェルスクリプトを通常実行した際に起動されたシェルは非インタラクティブです。
bash の man にはある通り、対話的な場合は $-
に i
が含まれ、非対話的な場合は i
が含まれません。
いくつか例を見てみましょう
$ echo $- himBH # インタラクティブ $ bash -c 'echo $-' hBc # 非インタラクティブ $ echo -e '#!/bin/sh\necho $-' > test.sh && chmod 777 test.sh $ ./test.sh hB # 非インタラクティブ
なお、$-
はシェル起動時に指定されたフラグの一覧を表す特殊変数です。
このように、インタラクティブかそうでは無いかを分別するのは、シェルの2面性が影響します。つまりコマンドインタプリタとしてユーザインターフェースを担う側面と、スクリプト言語としてプログラムの実行を担う側面です。
2面性が別の機能として提供されていれば、インタラクティブかそうではないかなどを場合分ける必要もなかったはずですが、シェルがこの2面性を抱えて機能拡張されてきた歴史上、このようになっています。
さて、インタラクティブ/非インタラクティブについては一つ注意点があります。
以下の man page にあるように、ネットワーク経由の場合はインタラクティブ扱いになります。
bash は、リモートシェルデーモン rshd やセキュアシェルデーモン sshd によって実行された場合など、標準入力がネットワーク接続に接続された 状態で実行されたかどうかを調べます。 この方法によって実行されていると bash が判断した場合、 ~/.bashrc が存在し、かつ読み込み可能であれば、 bash はコマンドをこのファイルから読み込んで実行します。
ssh localhost 'ls -l'
のようにリモートでコマンドを実行した場合は、ログインシェルでもなく、コマンドを直接指定しているので非インタラクティブなはずですが、ネットワークにつながっているとしてインタラクティブ扱いされて ~/.bashrc
が読まれます。
ログインシェルとインタラクティブシェルの分類
ここまで見てきたログインシェルとインタラクティブシェルはそれぞれ直行しており、ログインシェルでありインタラクティブシェルであったり、非ログインシェルであり、非インタラクティブシェルであったり組み合わせがあります。
ログインシェルとインタラクティブシェルの組み合わせを具体例にすると以下のようになります。
ログイン | 非ログイン | |
---|---|---|
インタラクティブ | su - [user] したり sshなどでリモートにログイン |
bash や su <user> で切り替えたシェル |
非インタラクティブ | デスクトップ画面にログイン(--login ) |
スクリプトやコマンド(-c)の実行 |
Linux の場合、デスクトップ画面からターミナルを起動した場合はインタラクティブな非ログインシェルとなります(~/bashrc
読み込み)。
一方、OSX ではターミナル起動時にログインコマンド経由で -bash
というコマンド名が指定されるため、インタラクティブなログインシェルが起動します(~/bash_profile
読み込み)。
初期化ファイルの読み込みが場合分けされるケースがわかったところで、各種環境においてデフォルトで用意される初期化ファイルがどのようになっているかを見てみましょう。
各種環境における初期化ファイル
設定ファイルはディストリビューション毎に様々です。
CentOS
Ubuntu
OSX
を例に、設定ファイルの読み込み部分を抜粋して見て行きます。
CentOSの初期化ファイル
CentOS の /etc/profile
は以下のように定義されています。
# /etc/profile # System wide environment and startup programs, for login setup # Functions and aliases go in /etc/bashrc ... for i in /etc/profile.d/*.sh /etc/profile.d/sh.local ; do # (1) if [ -r "$i" ]; then if [ "${-#*i}" != "$-" ]; then # (2) . "$i" else . "$i" >/dev/null fi fi done ... if [ -n "${BASH_VERSION-}" ] ; then # (3) if [ -f /etc/bashrc ] ; then . /etc/bashrc fi fi
(1) で /etc/profile.d/
以下にあるスクリプトを読み込んでいます。システムワイドな設定を変更する場合、直接 /etc/profile
を編集するのではなく、/etc/profile.d/
以下に設定ファイルを追加してカスタマイズするようになっています。
(2) は呪文でしか無いのですが、Remove Smallest Prefix Pattern で、${-#*i}
でシェル起動時に指定されたフラグの一覧から i
を除いたものを返します。これをフラグの一覧 $-
と比較することで、インタラクティブモードで起動したかどうかを判定しています。
インタラクティブモードの場合は、/etc/profile.d/
以下の内容をインポート、非インタラクティブモードの場合も同じくインポートしますが、出力を /dev/null
して捨てています(非インタラクティブの場合は定石です)。
(3) では $BASH_VERSION
で、bash 起動かを判定し(bash の場合バージョン番号が入る)、bash の場合は(ファイル存在チェックの後) /etc/bashrc
をインポートしています。
/etc/profile
からインポートされる /etc/bashrc
は以下のようになっています。
# /etc/bashrc # System wide functions and aliases # Environment stuff goes in /etc/profile # Prevent doublesourcing if [ -z "$BASHRCSOURCED" ]; then # (1) BASHRCSOURCED="Y" # are we an interactive shell? if [ "$PS1" ]; then # インタラクティブシェル? ... fi if ! shopt -q login_shell ; then # (2) SHELL=/bin/bash for i in /etc/profile.d/*.sh; do if [ -r "$i" ]; then if [ "$PS1" ]; then # インタラクティブシェル? . "$i" else . "$i" >/dev/null fi fi done fi fi
(1) では2重読み込みを避けるため BASHRCSOURCED
という変数でチェックを行っています。
(2) では、bash の組み込みコマンドである shopt で bash のオプションを得られるので、ログインシェルかどうかの判定を行っています。
ログインシェルでは無い場合は、/etc/profile
と同様に /etc/profile.d
配下の内容をインポートしています。
/etc/profile
から呼ばれた場合は、(/etc/profile
はログインシェルから呼ばれるため) /etc/profile.d/
配下のファイルの読み込みは行われません。
以上でシステムワイドな設定が終わり、続いてユーザ設定の読み込みが行われます。
CentOS では ~/.bash_profile
が以下のように用意されています。
# .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi PATH=$PATH:$HOME/bin export PATH
~/.bashrc
の読み込みと、PATH変数のエクスポートをしているだけです。
~/.bashrc
は以下のようになっています。
# .bashrc # User specific aliases and functions alias rm='rm -i' alias cp='cp -i' alias mv='mv -i' # Source global definitions if [ -f /etc/bashrc ]; then . /etc/bashrc fi
alias の設定と、/etc/bashrc
の読み込みだけです。
/etc/bashrc
は2重読み込みを行わないようになっているため、ログインシェルから /etc/profile
が実行された場合は /etc/bashrc
の処理は行われません。
一方、非ログインシェルで、インタラクティブシェルとして起動された場合は、~/.bashrc
経由で /etc/bashrc
による初期化が行われます。
Ubuntuの初期化ファイル
Ubuntu の /etc/profile
はどうなっているでしょう。
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1)) # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...). if [ "${PS1-}" ]; then # インタラクティブシェル? if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then # bash? if [ -f /etc/bash.bashrc ]; then # (1) . /etc/bash.bashrc fi else if [ "`id -u`" -eq 0 ]; then PS1='# ' else PS1='$ ' fi fi fi if [ -d /etc/profile.d ]; then for i in /etc/profile.d/*.sh; do if [ -r $i ]; then # (2) . $i fi done unset i fi
(1) では、インタラクティブシェルで、かつ bash の場合に /etc/bash.bashrc
を読み込んでいます。
なお、${PS1-}
はシェルの変数展開の ${parameter:-word}
や ${parameter-word}
の word
が空としている書き方です。
シェルスクリプトでは set -u
しておくと未定義の変数を使おうとしたときに処理を終了しますが、${PS1-}
はこれを回避する書き方です(CentOS では直接 $PS1
としていましたね)。
(2) では CentOS と同じように /etc/profile.d/
以下のファイルをインポートしています。
ユーザ設定は ~/.profile
が用意されています
if [ "$BASH" ]; then # bash? if [ -f ~/.bashrc ]; then . ~/.bashrc fi fi mesg n || true
bash であれば ~/.bashrc
の読み込みをしているだけですね。
~/.bashrc
は以下のようになっています。
# ~/.bashrc: executed by bash(1) for non-login shells. # If not running interactively, don't do anything [ -z "$PS1" ] && return # (1) # some more ls aliases alias ll='ls -alF' alias la='ls -A' alias l='ls -CF' # Alias definitions. if [ -f ~/.bash_aliases ]; then # (2) . ~/.bash_aliases fi
(1) では PS1 変数を調べ、非インタラクティブの場合は return で抜けています。
(2) では alias 用の設定ファイルを読み込んでいます。
macOS(Catalina以前)の初期化ファイル
macOS の /etc/profile
は以下のようになっています。
# System-wide .profile for sh(1) if [ -x /usr/libexec/path_helper ]; then eval `/usr/libexec/path_helper -s` fi if [ "${BASH-no}" != "no" ]; then # (1) [ -r /etc/bashrc ] && . /etc/bashrc fi
(1) で bash かどうかを調べ、bash の場合に /etc/bashrc
を読み込んでいます。
/etc/bashrc
は以下のようになっています。
# System-wide .bashrc file for interactive bash(1) shells. if [ -z "$PS1" ]; then # (1) return fi PS1='\h:\W \u\$ ' # Make bash check its window size after a process completes shopt -s checkwinsize [ -r "/etc/bashrc_$TERM_PROGRAM" ] && . "/etc/bashrc_$TERM_PROGRAM" # (2)
(1) で PS1 変数を調べ、非インタラクティブの場合は return で抜けています。
(2) では $TERM_PROGRAM
という変数で定義される Apple_Terminal
という文字列に該当する、bashrc_Apple_Terminal
というターミナル用の設定を読みこんでいます。
最近のMac ではユーザ設定ファイルはデフォルトでは用意されないようです。
macOS(Catalina)の初期化ファイル
macOS Catalina からは zsh がデフォルトのログインシェルおよびインタラクティブシェルになりました。
アップグレード後は、ターミナル起動時に以下のメッセージが表示されます。
The default interactive shell is now zsh. To update your account to use zsh, please run `chsh -s /bin/zsh`. For more details, please visit https://support.apple.com/kb/HT208050.
以下のようにすることで zsh をデフォルトに変更できます。
$ chsh -s /bin/zsh
/etc/profile
に相当する /etc/zprofile
は以下のようになっています。
# System-wide profile for interactive zsh(1) login shells. # Setup user specific overrides for this in ~/.zprofile. See zshbuiltins(1) # and zshoptions(1) for more details. if [ -x /usr/libexec/path_helper ]; then eval `/usr/libexec/path_helper -s` fi
/etc/bashrc
に該当する /etc/zshrc
は以下のようになっています。
# System-wide profile for interactive zsh(1) shells. # Setup user specific overrides for this in ~/.zhsrc. See zshbuiltins(1) # and zshoptions(1) for more details. # Correctly display UTF-8 with combining characters. if [[ "$(locale LC_CTYPE)" == "UTF-8" ]]; then setopt COMBINING_CHARS fi # Disable the log builtin, so we don't conflict with /usr/bin/log disable log # Save command history HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history HISTSIZE=2000 SAVEHIST=1000 # Beep on error setopt BEEP # ...略 # Default prompt PS1="%n@%m %1~ %# " # Useful support for interacting with Terminal.app or other terminal programs [ -r "/etc/zshrc_$TERM_PROGRAM" ] && . "/etc/zshrc_$TERM_PROGRAM"
初期化のシーケンスは bash の場合と同じようになっています。
~/.bash_profile
と ~/.bashrc
は以下のような対応になります。
macOS | macOS Catalina |
---|---|
~/.bash_profile |
~/.zprofile |
~/.bashrc |
~/.zshrc |
各環境におけるデフォルトの初期化ファイル
各環境のユーザ初期化ファイルの内容をまとめると以下のようになります。
~/.profile |
~/.bash_profile |
~/.bashrc |
|
---|---|---|---|
CentOS | なし | ~/.bashrc の読み込み、PATHのexport |
alias 定義、/etc/bashrc 読み込み |
Ubuntu | なし | ~/.bashrc の読み込み |
alias 定義、~/.bash_aliases 読み込み |
macOS | なし | なし | なし |
Linux の標準シェルは bash ですし、macOSも bash/zsh を標準としているため、~/.profile
は定義されていません。
~/.bash_profile
から ~/.bashrc
を呼ぶのは鉄板ですね。
~/.bashrc
は インタラクティブ時に読み込まれるため、bash を対話的に使う場合に必要な定義は ~/.bashrc
にまとめ(Aliasなど)、~/.bash_profile
はログインシェルで読み込まれるため、ログイン時に一度だけ実行すれば良いものを定義すればよさそうですね。
PASH変数は export することで環境変数となり子シェルに引き継がれるので、bash 起動のたびに行うものでもない ということで、~/.bash_profile
に環境変数を定義すると。
~/.bash_profile
は非インタラクティブ時(たとえばGUIによるログイン)にも呼ばれることを考えると、プロンプト設定などももちろん ~/.bashrc
に置くのが良いのでしょう。
では、以上を踏まえて、考えうる初期化ファイルの管理方針をいくつか見ていきましょう(なお、ここでは ~/.shrc
などは扱いません)。
単一ファイル管理(~/.bashrc
)
~/.bash_profile
から ~/.bashrc
が呼ばれるので、カスタマイズするのは ~/.bashrc
だけで良しとする方針です。
# .bash_profile if [ -f ~/.bashrc ]; then . ~/.bashrc fi
# .bashrc [ -z "$PS1" ] && return # 非インタラクティブの場合は return # ここにお好きな設定を記載 # エイリアス・環境変数・シェルオプション・プロンプト設定
どこに何を書けばよいかを迷わなくて済む利点はありますが、~/.bashrc
でPATH変数を export
した場合に(export PATH="$HOME/hoge/bin:$PATH"
)、bash 起動毎に同じPATHが連結(home/hoge/bin:home/hoge/bin
のように)されてしまう点は、実害は無いかもしれませんが、気持ちいいものではありません。
上記 ~/.bashrc
ではインタラクティブ時のみ実行のガードを入れていますが、GUI 起動するプログラムなどに環境変数が渡らなくなってしまうのも良くない場合があるでしょう。
これらを気にしないのであれば、管理コストが低いので採用しても良いかもしれません。
標準的管理(環境変数は~/.bash_profile
)
ログイン時に一度だけ行えば良い環境変数の定義は~/.bash_profile
で行い、その他の対話的操作で必要な設定を ~/.bashrc
に行う方針です。
おそらくこれが多数の管理方針と思います。
# .bash_profile if [ -f ~/.bashrc ]; then . ~/.bashrc fi # ここに環境変数を定義 export PATH="$HOME/hoge:$PATH"
# .bashrc [ -z "$PS1" ] && return # 非インタラクティブの場合は return # ここにお好きな設定を記載 # エイリアス・シェルオプション・プロンプト設定
ファイルが別れて少し面倒ですが、先の例の問題点は解消されますし、なにより一般的です。
ただ、zsh を使ったり、ログインシェルが sh だったりと、別のシェルを使い分けるのであれば、もう少し厳格な管理が必要になります。
厳格管理(~/.profile
利用)
~/.bash_profile
では ~/.profile
と ~/bashrc
の読み込みだけを行い、環境変数は ~/.profile
に定義し、bash の対話的操作で必要なものは ~/bashrc
に書く方針です。
# .bash_profile if [ -f ~/.profile ]; then . ~/.profile fi if [ -f ~/.bashrc ]; then . ~/.bashrc fi
# .profile # ここに環境変数を定義 export PATH="$HOME/hoge:$PATH"
# .bashrc [ -z "$PS1" ] && return # 非インタラクティブの場合は return # ここにお好きな設定を記載 # エイリアス・シェルオプション・プロンプト設定
bash 以外のシェルも考慮して ~/.profile
を用意(sh はこのファイルを読みます)します。ここでは他の設定ファイルのインポートのみを行います。
bash 以外の初期化ファイルからも~/.profile
をインポートすることで、環境変数が共有できます。
~/.profile
にはGUIアプリで使うものや bin/sh
で使うもの、シェルの種類に依存しないものを定義します。
そして ~/.bashrc
には bashでしか使わない対話的操作で必要なものを定義します(標準出力や標準エラー出力へ書き込む処理は記載しない)。
macOS Catalina の場合
# .zprofile if [ -f ~/.profile ]; then emulate sh -c 'source ~/.profile' fi if [ -f ~/.zshrc ]; then . ~/.zshrc fi
# .profile # ここに環境変数を定義 export PATH="$HOME/hoge:$PATH"
# .zshrc [ -z "$PS1" ] && return # 非インタラクティブの場合は return # ここにお好きな設定を記載 # エイリアス・シェルオプション・プロンプト設定 # 補完など # autoload -U compinit # compinit
まとめ
本記事では、bash の初期化ファイルの読み込みについて整理し、いくつかの管理方針を例示しました。
どのように管理すべきかは利用環境毎に自身で判断し、わかりやすくなっていれば問題ないと思いますし、何が正解かもそれぞれです。
本記事が初期化ファイルの運用の一助になれば幸いです。