Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

Bash: .bashrcと.bash_profileの違いを今度こそ理解する

更新情報

  • 2019/06/06: 初版公開
  • 2021/07/08: 更新

こんにちは、hachi8833です。社内Slackで見かけたmorimorihogeさんの以下の書き込みで目から鱗が落ちました。

~/.bashrcで何かを出力してしまうと、rsyncなどのsshパイプで問題が生じることがあるそうです。

参考: 知らないとrsyncでもハマるシェル初期化 - Qiita

これをきっかけに、できるかぎり一次情報を元になるべく一般的になるようにまとめてみました。

シェルスクリプト(.bashrcや.bash_profileなども含む)はあまりに自由に書けてしまい、LinuxディストリビューションやmacOSによって作法がまちまちだったりするので、外してはいけないポイントがどこかを知りたかったのでした。

対象はbashとsh(Bourne Shell)に限定します。また、デスクトップGUIの設定ファイルについては最小限にとどめます。

参考

.bashrcの「rc」は「run command」の略だそうです。

bashのmanページ

ディストリビューションに依存しない一次資料となるとやはりman bashだろうというアドバイスがmorimorihogeさんからありました。

bash が対話的なログインシェルとして起動されるか、 --login オプション付きの非対話的シェルとして起動されると、/etc/profileファイルが存在すれば、 bash はまずここからコマンドを読み込んで実行します。 このファイルを読んだ後、 bash は ~/.bash_profile, ~/.bash_login, ~/.profile をこの順番で探します。 bash は、この中で最初に見つかり、かつ読み込みが可能であるファイルから コマンドを読み込んで実行します。
同日本語manページより

bashのmanページは長大かつ非常に詳細です。「どう動くか」についての説明はものすごく詳しいのですが、「どう運用すべきか」についての説明はほとんどありません。

正直、日本語になってても非常に読みづらいのですが、細かな点については最終的にここで確認するのが確実だと思います。

superuser.comの回答

これまたmorimorihogeさんがsuperuser.comの回答の中から「いいね」数の多い回答を抜粋してくれました。

回答1

以下に要点を補足しつつ記します。

  • Unixシステムでは「プログラムを起動するためのプログラム」が起動される。これがコマンドラインシェル(いわゆるシェル)と呼ばれる。
  • デフォルトのシェルはBourne shell(いわゆる/bin/sh)と呼ばれ(Bourneは作者の名前)、「ログインシェル」として起動するときに~/.profileを読み込む。
  • bash(Bourne Again shell)はBourne shellに似ているがより高機能で、「ログインシェル」として起動すると~/.bash_profileを読み込む。~/.bash_profileがなければ~/.profileを読み込もうとする。
  • シェルがログインシェルではない形で起動する場合は、~/.profileを読み込まない。
  • bashを対話的シェル(=スクリプト実行用ではないということ)として起動するときは、~/.bashrcを読み込む。

上を踏まえて、次のように使い分けることが勧められています。

設定ファイル 利用法
~/.profile ログイン時にそのセッション全体に適用するものを記述する
シェルの種類に依存しないものを記述する
・環境変数など
~/.bashrc bashでしか使わないものを記述する ・エイリアス
・シェルオプション
・プロンプト設定
~/.bash_profile ~/.profileと同じに使えるが、bashのみで有効
  • bashを対話的に使う場合、~/.bash_profileに次を書いておくことを推奨する。
    • .profileがあれば読み込む設定
    • bashが対話モードであれば~/.bashrcを読み込む設定
  • 「環境変数も~/.bashrcに書け」とか「ターミナルで常にログインシェルを起動しろ」と言ってる記事がよくあるが、どちらもマズいやり方。
    • これをやるとターミナル以外から起動するプログラム(GUI起動など)に環境変数が渡らなくなってしまうのが最大の問題。

回答2

こちらも要点を以下にまとめます。

設定ファイル コツ
~/.profile ・bashに依存しないものだけを書く
・GUIアプリで使うものやbin/shで使うものはここに置く(必須)
・ログインシェルで使うものはここに置くべき
・環境変数
PATH変数
・etc
~/.bashrc ・対話モードで使うものはすべてここに書く
・ここでは何も出力してはならない
・エイリアス
EDITOR変数
・プロンプト設定
・etc
~/.bash_profile ・余計なものは極力書かない
・右の順に読み込むだけにする
~/.profileがあれば読み込む
~/.bashrcがあれば読む
  • ~/.bash_loginは存在しないようにしておこう

冒頭のスクショと同様、~/.bashrcからは(標準出力や標準エラーに)何も出力してはならないと注意されていますね。

まとめ

上の回答は、設定ファイルの振る舞いや意義を手短に解説しつつ、bash以外でも極力使うにはこう書くのがよいというアドバイスになっています。

そこまで厳密に書かなくても動くのが設定ファイルですし、書き方は人それぞれになると思います。

しかし、少なくとも~/.bashrcで何も出力してはならない、という部分は外さないようにする必要があります。出力があるなら/dev/nullに捨てるなどしましょう。

業務で共有するシェル環境のカスタマイズは最小限にしよう

自分のマシン内のプロファイルで自分だけが使うシェル環境であれば、心ゆくまでカスタマイズして構わないと思います。

逆に、業務で使うLinuxサーバーなどで共有するプロファイルや環境変数については、あまりかっ飛んだカスタマイズをすると他の人がメンテするときに支障が生じる可能性があります。

特に、サーバープロセスで使うユーザーについては不必要なカスタマイズを極力避けたいと思いました。

もちろん、業務で使うサーバーでも、作業者が自分のプロファイルを作って作業する分にはある程度好きにカスタマイズできると思いますが、作業手順が自分のプロファイルに依存すると作業の引き継ぎが面倒になるので、やはりカスタマイズは控えめがよさそうです。

参考

自分のコンフィグ

私も、ローカルVMのUbuntu 18.04.1 Server LTS環境で上のアドバイスに沿って、.profileと.bashrcと.bash_profileを設定しました。

  • ~/.bash_profile: .profileと.bashrcの存在確認&読み込みだけ(何も足さない)
  • ~/.profile: 環境変数やGUI設定はここに書く(bashに依存しないもの)
  • ~.bashrc: bashに依存する対話モード向け設定はここに書く
    • 念のため標準出力や標準エラー出力に出力しないよう注意する
    • 設定は、.bashrc冒頭の非対話モード脱出コードの後に置く(後述)
  • ~/.bash_alias: (自分で追加)エイリアスはここに書く
  • ~/.bash_loginは使っていません)

自分の場合エイリアスの追加頻度が多いので、.bash_aliasという別ファイルを作成してそこに書くようにし、.bashrcで.bash_aliasを存在チェックしつつ読み込むようにしています。

また、macOSのbash設定もこのUbuntuのbash設定と同じ方針で設定し、そこにmacOS固有の設定を追加しました。

追記(2019/06/07)

上に書いた私の設定方針は、自分だけが普段づかいするbash環境向けであり、今後自分の環境でbash以外のシェル(zshとかfishとか)を試してみる可能性や、LinuxデスクトップGUIを試す可能性(しないと思いますが😆)をうっすら考えています。

少なくともデスクトップGUIやbash以外の環境を使いそうにない業務用Linuxサーバーでは、.profileがない場合にわざわざ作成したり、エイリアス用設定ファイルまで置いたりするのはトゥーマッチになることがあるかもしれないと個人的に感じています(プロジェクトでポリシーが決められていれば別)。

参考: Ubuntu Server LTSのデフォルト設定

Ubuntu 18.04.1 Server LTSは、デフォルトでは~/.profileで対話モードかどうかを判断し、対話モードの場合に~/.bashrcを読み込むようになっています。

ただし、~/.bash_profile~/.bash_loginがある場合は対話時に~/.profileではなくそれらを読み込みます。

# `~/.profile`
# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"
    fi
fi

かつ、デフォルトの/etc/bash.bashrc~/.bashrcの冒頭にも、それぞれ以下の設定が記載されています。

# /etc/bash.bashrc
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
# ~/.bashrc
# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

いずれも、bashが対話的でない場合は即脱出し、それより後の行なら何を書いても決して出力されることのないようになっています。

つまり、~/.bashrcが非対話モードで誤って何かを出力しないよう、二重に対策が行われています。

参考: CentOS 7のデフォルト設定

CentOS Linux release 7.5.1804 (Core)の場合、以下のようになっていました。

CentOSでは、従来よくある方式と同様、~/.bash_profile~/.bashrcが存在するかどうかを調べ、存在すれば読み込むようになっています。

# ~/.bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

一方、/etc/bashrcでは、対話的な場合にはプロンプトの設定などを行っていますが、非対話の場合の脱出は特に行っていません。

# /etc/bashrc
# 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
      elif [ "${VTE_VERSION:-0}" -ge 3405 ]; then
          PROMPT_COMMAND="__vte_prompt_command"
      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

かつ、~/.bashrcは単に/etc/bashrcに丸投げしています。

# ~/.bashrc
# Source global definitions
if [ -f /etc/bashrc/ ]; then
  . /etc/bashrc
fi

非対話かつログインでないシェルであれば/bin/shが動くから~/.bashrc~/.bash_profileも読み込まれないはずなので、Ubuntuのように脱出の条件までは書かないという考え方なのかなと推測しました。

おたより発掘

関連記事

Linux CUI初心者に早く知っておいて欲しいコマンド操作


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。