104
123

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【完全版】歴史でシェルの設定ファイルを理解する - 全POSIXシェル対応 (.profie, .bash_profile, .bashrc, .zprofile, zshrc, etc.)

Last updated at Posted at 2024-11-03

はじめに

毎度おなじみ、いつものシェルの歴史研究家(自称)です。今回は誰もが雰囲気で使っているはずのシェルの設定ファイルのお話です。シェルの設定ファイルって、いっぱいあってなんだかよくわからないですよね? どういうファイルがあってどういう順番で読み込まれるのか、どこに何を書けばいいのか? この記事はそのシェルの設定ファイルを完全に理解できる・・・ことを目指す記事です。しかも全 POSIX シェル対応です。タイトルの「完全版」の意味は、POSIX シェルの設定ファイルのすべてを書いたという意味です。まあ書いてないこともあると思いますが、重要なことが抜けていれば追記します。

私がシェルの設定ファイルを理解できたのは、全 POSIX シェルの設定ファイルのことを調べたからです。bash だけしか知らなければ、bash 固有の考え方なのか、普遍的な考え方なのか区別がつきません。そしてシェルの設定ファイルの歴史も調べました。シェルは長く使われており歴史があります。歴史が長いものはそれが開発された当時の状況を知らないと理由を理解できないことがあります。この記事には私が調べたシェルの設定ファイルの歴史の話がおまけとして含まれています。歴史に興味がある人にもオススメです。

この記事は三部構成です。

  • 第一部は特定のシェルに依存しない、シェルの設定ファイルの考え方を説明しています。
  • 第二部は各シェルごとの設定ファイルの読み込み方法について説明しています。自分が知りたいシェルだけ読めば十分です。
  • 第三部はシェルの設定ファイルの歴史の解説です。この記事の本編なので、歴史に興味がある人だけどうぞ。

この記事では「シェルを起動」と書いた場合、対話シェル(インタラクティブシェル)を起動することを意味します。明示的に書いている場合を除き、シェルスクリプト用の非対話シェル(ノンインタラクティブシェル)の話ではありません。

  • 👉️対話シェル ・・・ 人が対話的に使うシェルでログインシェルも含まれる
  • 🙊非対話シェル ・・・ シェルスクリプトを実行するためのシェル(こっちの話はしない)

あと、よくある「結局どっちに書けばいいの?」の答えだけを知りたい人に書いておくと「.bashrc または .zshrc に全部書いておけばいいよ」で、まあざっくりだいたい問題ないので、この記事を読む必要はありません。また、さまざまな所で「シェルスクリプトで読み込まれる .zshenv」を使おうとするのを見かけますが、特殊な場合でどうしても必要なとき以外は .zshenv を使わないでください

🔰 この記事の内容やこの記事へのコメント(Twitter、はてなブックマーク、その他の外部サービスの公開コメントを含む)に対する私の返信の方針についてはこちらを参照してください。

第一部 シェルの設定ファイルを理解する

ここで説明するのはすべての POSIX シェルに共通した話です。実は各シェルの設定ファイルに関する考え方は(少なくとも POSIX シェルに関しては)ほぼ同じです。シェルごとに大きな違いがあるわけではないので、個別に理解する必要はありません。

よく見かける説明で理解できない理由

シェルの設定ファイルに関する説明はあちこちに書かれていますが、それを読んでも(私は)よく理解できませんでした。ログインシェルでは、最初に .profile ファイルを読み込んで、次に .bashrc が・・・みたいな説明をよく見かけるのですが、詳しい説明ほど頭が混乱するだけで、最後に思うのは「それでいったいどうしたらいいの?」です。理解した今ならわかるのですが、本当に説明しなければいけなかったのは「設定ファイルの役割」と「設定ファイルの読み込みはどうあるべきか」です。それを理解していなかったから設定ファイルの読み込み順をいくら読んでもわからなかったのです。設定ファイルを理解するとは読み込み順を知ることだという考えが間違いだったわけです。設定ファイルを理解すれば逆になんでこのシステムではこんなやり方をしてるの?と考えたりケチを付けたりできるようになります。

よく見かける説明のもう一つのダメなポイントは環境ごとに違いがあることを書いていないことです。特に bash は Debian 系と Red Hat 系で設定ファイルの構成が違うのですが、書いている人が違いがあることを知らないのか、なんの説明もなしに Debian 系または Red Hat 系を前提として書いています。例えば全ユーザーに共通する設定ファイルは Debian 系では /etc/bash.bashrc でユーザーの設定ファイルよりも前に自動で読み込まれます(SYS_BASHRC-DSYS_BASHRC について調べよ)が、Red Hat 系では /etc/bashrc でユーザーの設定ファイルから手動で読み込みます。他の環境では違う場合もあるでしょう。このような違いがあるのに、その違いを説明せずに進める記事を読んでも混乱するのは当然ですよね。

「対話シェル(インタラクティブシェル)」と「ログインシェル」という比較の仕方をしているのも問題です。ログインシェルは対話シェルの一種です。本当の関係性は「対話シェル vs 非対話シェル」「ログインシェル vs 非ログインシェル」です。「非対話シェル and ログインシェル」という状態は普通は考えられず省略されることが多いため、次の3種類のモードが有るかのように説明されることが多いです。

  • 対話シェル and ログインシェル
  • 対話シェル and 非ログインシェル
  • 非対話シェル and ログインシェル
  • 非対話シェル and 非ログインシェル

4 つのモードがあるわけでもありません。これは単なる独立した2種類(対話か否か、ログインか否か)の実行モードの組み合わせです。この記事では基本的に対話シェルしか扱わないので、区別はログインシェルであるか否かしかありません。

よく見かける説明で理解できないもう一つの理由は、現在の使い方や時代の変化を考慮して書かれていないからです。現在のシェルの一般的な使い方はターミナルソフトから起動することです。しかしシェルの設定ファイルを説明したような文書はたいてい古く、ターミナルソフトの話が欠けています。よく調べて書かれた説明でも、そのようなところが抜け落ちてしまっているために、中途半端な説明になってしまっています。

知らなくて良い設定ファイルは忘れよう

まずは知る必要のない設定ファイルの話を忘れましょう/etc 以下にある設定ファイル(/etc/profile など)は忘れてください。知る必要はありません。知らなくてもホームディレクトリ以下の設定ファイルのことを知れば、なんとなくわかります。そしてなんとなくわかれば十分です。そもそも /etc 以下の設定ファイルは OS が提供するシステムファイルの一部で、ものすごく凝って作られており、環境ごとに違います。はっきり言ってこんなの理解できませんし、理解したところで他の環境に応用できません。/etc 以下はパッケージ管理システムからパッケージをインストールしたときに、ちゃんと動くように OS がいろいろやるところです。不用意に /etc 以下のファイルを書き換えないようにしてください。例えば /etc/bashrc にはわかっていないなら書き換えるなと書いてあります。基本的には、末尾の .d/ のディレクトリにファイルを追加しますが、システム管理の話なのでしません。まず知らなければいけないのはホームディレクトリ以下の設定ファイルだけです。Unix/Linux やシェルの使い方に慣れて、OS 内部の仕組みに興味が出たら、その時は /etc 以下の仕組みを調べてください。

ホームディレクトリ以下の設定ファイルも「プロファイル」と「rcファイル」しか知る必要はありません。.bash_login.bash_logout.zlogin.zlogout なんて C シェルユーザーが乗り換えやすくするためにあるようなものですし、もし本当に必要な時が来たら名前からわかるはずです。特殊な用途のための .zshenv はシェルスクリプトでも読み込まれるので、気づかずに使っているとトラブルの元になります。知るべき設定ファイルはプロファイルとrc ファイルの2つです。ほら、話は一気に簡単になったでしょう? シェルの設定ファイルを理解するのに、シェルの違いを考える必要もないので、すべての POSIX シェルの設定ファイルを理解するのも簡単なんですよ。

「プロファイル」と「rcファイル」

設定ファイルで知らなければいけないのはホームディレクトリ以下のプロファイルとrcファイルだけです。プロファイルは名前に profile が含まれているファイルの総称で、rc ファイルは名前に rc が含まれているファイルの総称です。

  • プロファイル ・・・ .profile, .bash_profile, .zprofile など
  • rc ファイル ・・・ .shrc, .bashrc, .zshrc など

プロファイルと rc ファイルはやるべきことが違います。やるべきことというか、できることが違うと言ったほうが正しいでしょう。できることに違いがあるのだから、使い分けなんてすぐにわかります。

環境の設定 シェルの設定 補足: 読み込むタイミング
プロファイル 🟢 できる ❌️ できない ログイン直後の時点で読み込む
rc ファイル 🟢 できる 🟢 できる シェルを起動するたびに読み込む

プロファイルでできることは環境の設定だけです。シェルの設定は実際にはできないことはないのですが、やっても無意味なことになるのでできないとします。無意味なことになるというのは新しく起動したシェルにはプロファイルで行うシェルの設定は反映されないということです。環境の設定とは、特定のシェルに依存しない初期化処理のことで、その一つが環境変数の設定です。環境変数は OS の機能であってシェルの機能ではありません。環境の設定には、他に stty コマンドによる端末の設定や umask コマンドによる umask の設定などがありますが、プロファイルで設定することはあまりありません

rc ファイルでは環境の設定とシェルの設定の両方ができます。シェルの設定、例えばプロンプト文字列の設定やシェルの機能を有効にしたり補完スクリプトの読み込みなどは rc ファイルに書きます。つまり、ほとんどのことは rc ファイルで設定します。rc ファイルの読み込むタイミング、「シェルを起動するたびに読み込む」の意味ですが、これはログインした後にコマンドラインから bashzsh と入力して別のシェルに切り替えたり、テキストエディタの中からシェルを実行することです。そのようなことをしない人は全くしないかもしれませんが、そういう時の話です。シェルの設定はシェルごとに違うため、rc ファイルは原則としてシェルごとに異なる設定ファイルが必要です。ただしエイリアス設定など共有できる部分もあります。

なぜプロファイルとrcファイルの2つの設定ファイルがあるのかですが、最初はプロファイルしか設定ファイルがなかったからです。しかしプロファイルの読み込みタイミングではシェルの設定ができなかったので、後から作られたのが rc ファイルです。この話は第三部の歴史の話で詳しく説明します。

プロファイルとrcファイルの読み込み順

プロファイルはログイン時(ログインシェル)でしか読み込まれません。ターミナルソフトからシェルを起動したときもログイン時とします。正確にはターミナルソフトの設定によるのですが、実用という観点からログインした時と同じように振る舞うべきであり、最近のターミナルソフトはデフォルトでそういう挙動になっているのが多いはずです。rc ファイルは(対話シェルなら)常に読み込まれます。ログイン時でもログイン時じゃなくて読み込まれます。それはそうです。ログイン時でもログイン時じゃなくてもシェルの設定は必要だからです。ここでこの説明が間違っていると思った人は、rc ファイルは常に読み込むものだと騙されてください。些細な話としてプロファイルは rc ファイルよりも先に読み込まれます。読み込み順について知る必要があるのはこれだけです。知る必要がないものを省けば、他の詳しすぎる図と比べてここまで単純化できます。

shell-settings.drawio.png

実際のシェルでは、シェルによって rc ファイルを読み込ませる手法に違いがあるため、ここに書いた通りではありません。ここに書いているのは考え方です。実際のシェルの動作に関しては第二部で説明します。

非対話シェルでrcファイルを実行しない

非対話シェルとは、シェルスクリプトを動かすインタプリタとして使うときのシェルのことです。一般的に、非対話シェルは設定ファイルを読み込みません。シェルによっては読み込ませる方法もありますが、一般的にシェルスクリプトは、シェルのデフォルト状態で動かすことを想定しており、設定ファイルでシェルの状態を変えてしまうとおかしなことになってしまいます。シェルスクリプトでの設定ファイルの読み込みは特殊な用途でのみ使う機能です。シェルスクリプトを動かすときにプロファイルや rc ファイルに書いてある環境変数が必要であれば、シェルスクリプトに埋め込むか別のファイルを使って必要な環境変数だけを設定すると良いでしょう。少なくともシェルの設定を変更してしまう可能性があるものを読み込んではいけません。

詳細は第二部の bash の話でしますが、.bashrc などの「rc ファイルで標準出力や標準エラー出力に出力を行っても構いません」。間違いではありません。.bashrc で出力を行っても構いません。ただし非対話シェルでは、rc ファイルを実行してはなりません。先ほど非対話シェルは設定ファイルを読み込まないと言ったばかりですが、読み込む例外ケースが存在します。だから非対話シェルでは rc ファイルを実行してはいけませんと明示的に言う必要があります。そして非対話シェルで .bashrc を実行しないのであれば、(対話シェルのために).bashrc で出力を行っても構いません。.bashrc で標準出力や標準エラー出力に出力することを許さないと、対話シェルの起動時に任意の情報を出力することができなくなってしまいます。

対話シェルと非対話シェルを見けるには $- 特殊シェル変数を参照するのが推奨です。$- にインタラクティブの i が含まれていれば対話シェルです。bash の場合は非対話シェルで削除される PS1 シェル変数を参照する方法もありますが、本来 PS1 はプロンプト文字列を設定するためのシェル変数で、他のシェルでは非対話シェルでも削除されない場合があります。もし rc ファイルの中で非対話シェルで実行されているのを検出したら、それ以上実行しないようにすぐさま return します。

.profileはBourneシェル用のプロファイル

bash は .profile.bash_profile のように 2 つのプロファイルに対応しています。名前から気づいている人も多いと思いますが、.bash_profile は bash 専用のプロファイルで、.profile は Bourne シェル用のプロファイルです。bash は互換性を保つために .profile にも対応しています。不思議なことになぜかこの説明をちゃんとしているところをあまり見かけません。bash は .bash_profile を読み込みますが、.bash_profile が見つからない場合に .profile を読み込むのは Bourne シェルとの互換性のためなんです。

私は何度も書いていますが、Bourne シェルは主に古い商用 Unix で使われていたシェルで、最近の人で使ったことがある人は少ないはずです。少なくとも Linux、FreeBSD、NetBSD、OpenBSD、macOS しか使ったことがない人は Bourne シェルを使った経験はありません。Bourne シェルは古すぎて POSIX に準拠していません。POSIX は OS のインターフェース(主に C 言語用の関数)の標準規格ですが、シェルや Unix コマンドの一部も標準化しています。Bourne シェルが POSIX に準拠していない理由は、POSIX でシェルが標準化された頃(1992年)には Bourne シェルの開発はほぼ終了していたからです。Bourne シェルは POSIX にこだわる人はとっくに捨てたシェルです。Bourne シェルが公式に Linux、FreeBSD、NetBSD、OpenBSD、macOS に移植されたことはありません。非公式の移植版や後継シェルが一応あるにはありますが、そのようなものを使ったことがある人はほとんどいないでしょう。え? /bin/sh なら使ってるけど? と思っている人は、sh が Bourne シェルのことだと勘違いしていますsh は Unix で代々受け継がれてきた名前で、初代は Thompson シェル、2代目(分家)は PWB シェル、どちらも Bourne シェルと互換性はありません。POSIX で標準化されている sh は POSIX sh であって Bourne sh ではありません。POSIX sh は Bourne sh よりも高機能です。sh を名乗るシェルは常に変化しており、現在では別の人や団体が作った、dash や bash などに代替わりしています。20 年前ならいざ知らず、sh が Bourneシェルのことだと言うのはもう終わりにしましょう。

話を戻して、.profile ですが、これは元々 Bourne シェル用のプロファイルです。bash は Bourne シェルの上位互換シェルとして作られたため、.profile を読み込みます。またその他の POSIX シェルも .profile を読み込む場合があります.profile はさまざまなシェルが読み込むため、理想的には POSIX シェルの範囲の文法か、Bourne シェルでも共有したいのであれば、Bourne シェルの範囲の文法で書く必要があります。面倒だなと思うかもしれませんが、そもそもプロファイルでやることは環境変数の設定ぐらいしかなく、シェルの設定は行わないので、すべての POSIX シェルで読み込めるように書くのは難しくありません。

環境変数設定はrcファイルに書いて良い

環境変数の設定はプロファイルでも rc ファイルでも構いません。環境変数は子プロセスに継承されるので理屈の上ではプロファイルで十分ですが、だからといって「環境変数だからプロファイルに書かなければいけない」ことにはなりません。環境変数を .bashrc に書いたっていいんです。実際 Red Hat 系 Linux の標準のrcファイル (.bashrc) では、環境変数 PATH を設定しています。

# User specific environment
if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]]
then
    PATH="$HOME/.local/bin:$HOME/bin:$PATH"
fi
export PATH

おまけですが、上記のコードはすべての POSIX シェルで動作せず、ちょっとした潜在的なバグもあるため、参考としてすべての POSIX シェル対応(Bourne シェルにも対応)の同等のコードを提示しておきます。

case ":$PATH:" in
  *":$HOME/.local/bin:$HOME/bin:"*) ;;
  *) PATH="$HOME/.local/bin:$HOME/bin${PATH+:}$PATH"
esac

環境変数の設定をrcファイルで行った場合のよくある問題は、シェルを起動するたびに環境変数 PATH にパスが重複登録されてしまうことです。これは既存の PATH に追加という処理を行うためです。先程のコードはそれを防ぐために、追加したいパスが PATH にすでに含まれていないかを確認していました。PATH の問題があるから環境変数はプロファイルに書くべきだという意見はわからなくもないのですが、PATH だけの問題のために環境変数全てをプロファイルに書く必要はありません。関連する設定箇所を一箇所にまとめたほうがわかりやすいという意見もあるはずです。この問題の解決方法については以下で PATH から重複したパスを削除する方法を提示しています。

rc ファイルに環境変数の設定を書いたときの問題は、新たなシェルを起動すると環境変数の値がリセットされてしまうことです。もしrcファイルで環境変数 EDITORvim に設定するコードを書いていた場合、次のようなことになってしまいます。

$ echo $EDITOR
vim

$ EDITOR=nano

$ bash
$ echo $EDITOR
vim

この問題はほとんどの人が気にしないと思います。気になる場合は環境変数の設定をプロファイルに移動することで解決できますが、rc ファイルのままでも次のように書き換えれば対処できます。

# export EDITOR=vim
# の代わりに次のように書けば良い
export EDITOR="${EDITOR-vim}"

「すべての設定をプロファイに書く」のは、解決困難な問題が発生するケースがあるため間違いと言えますが、「すべての設定をrcファイルに書く」のは間違いではありません。それは単なる好みの問題です。

「ログインシェル」の意味とプロファイルを読み込む仕組み

シェルの設定ファイルの話では「ログインシェル」という言葉がよく出てきます。あれ、よくわからない用語ですよね? なんだよログインシェルって? あれ、用語が悪いんですよ。シェルがプロファイルを読み込むのは、シェルが「ログインモード」で起動されたからです。確かにシステムがログインシェルを起動する時、「シェルをログインモードで起動」します。しかし別にログインシェルでなくとも、シェルをログインモードで起動すれば、プロファイルを読み込みます。ログインモードという用語はシェルの公式の用語としては使われていませんが、ログインシェルよりもよっぽどわかりやすい用語だと思います。

シェルをログインモードで起動するのは簡単です。bash の場合 -l (--login) オプションを指定するだけです。シェルをログインモードで起動すると(rc ファイルの読み込み前に)プロファイルを読み込みます。

シェルをログインモードで起動する
$ bash -l

少し話が横道にそれますが、(ターミナル用の)あなたの好きなテキストエディタはなんでしょうか? vi? emacs? nano? まあ何でも良いのですが、例えば git commit を実行したときに、自動的にテキストエディタが起動しますよね? git config で使用するエディタを変更できますが、もう一つ環境変数 EDITOR を変更しても使用するエディタを変更できます。知っていました?

$ export EDITOR=nano
$ git commit

環境変数 EDITOR は「あなたが好きなエディタ」を設定する環境変数です。そしてですね。環境変数 SHELL は「あなたが好きなシェル」を設定する環境変数です。例えば Vim から「:shell コマンド」を実行するとテキストエディタを終了せずに、シェルを起動することができます。その時に起動するシェルはあなたが好きなシェル、つまり環境変数 SHELL に設定されているシェルです。だからあなたは環境変数 SHELL に自分が好きなシェルを設定しなければいけません。でも、あなたが好きなシェルなんて、あなたがログインしたときに使うと決めたシェル(それがログインシェル)に決まってるじゃないですか? だからシステムはあなたがログインしたときに、あなたがログインシェルとして設定したシェルを環境変数 SHELL に自動的に設定してくれるんです。便利ですね。ちなみにあなたがログインシェルとして設定したシェルは、例えば finger コマンドや getent コマンドで知ることができます。/etc/passwd から探しても構いませんがない場合もあります。

$ finger -m user
Login: user                             Name:
Directory: /home/user                   Shell: /bin/bash
Never logged in.
No mail.
No Plan.

注意: macOSは/etc/passwdにユーザー情報が含まれてないのでこれではわからない
$ getent passwd user
user:x:1000:1000:,,,:/home/user:/bin/bash

ということで何の話かというと、環境変数 SHELL は「今使っているシェルのことだという間違った説明」をよく見かけますが、本当はあなたがログインシェルとして指定したシェルのパスが自動的に設定されている環境変数です。あなたが現在使用しているシェルは echo $0 で調べますps $$ でも良い)。$0 は現在実行しているプログラム名です。

現在実行しているプログラム名(シェルの場合はシェル名)の出力
$ echo $0
-bash

さて話は戻ってまいりました。上記の bash の頭についている - は一体何なんでしょうか? シェルを「ログインモード」で実行するときには、bash だと -l オプションを指定します。ほとんどのシェルは -l オプションを持っていますが、FreeBSD sh は(少なくとも FreeBSD 14 時点では)-l オプションを持っていません。ルーツが同じ NetBSD sh や dash は持っているのにですね。元々シェルは -l オプションを持たせなければならないという仕様はないのでそういうものなんですが、それでは -l オプションがないのにどうやってシェルをログインモードで起動すればよいのだ?という話です。ここで出てくるのがシェルを頭に - をつけた名前で起動するという裏技(?)です。

シェルは必ず -l オプションを持っているとは限りません。そのためシステムはログイン時に起動するシェルを頭に - をつけた別名で起動します。シェルは自身が頭に - をつけた名前で起動されていたらログインモードで起動されたと判断します。これは昔からの慣習です。つまり echo $0 したときに出力された -bash-l オプションを指定する代わりだということです。ちなみにシェルを別名で起動するということをシェルでやるにはサブシェルと exec コマンドを組み合わせて -a オプションで指定すればできます。env コマンドが対応していれば良いと思うのですが、残念ながらそんな便利な機能はありません。

bashを「-bash」という名前で起動してもログインモードになる
注意: イメージです。これは動きません。
$ env -a -bash bash

↑でやってることと同じことを↓でできる

$ (exec -a -bash bash)

ちなみに exec コマンドの -a オプションは、bash、ksh、zsh などでは実装されていますが、FreeBSD shなどでは実装されておらず、POSIX でも標準化されていません。

ps コマンドを別名で起動すれば、実際に名前が変わっていることがわかります。実はこんなことができちゃうんです。

psコマンドを別名で起動する
$ (exec -a ----PS ps -f)
UID          PID    PPID  C STIME TTY          TIME CMD
koichi      1461    1460  0 Oct10 pts/4    00:00:00 -bash
koichi      4129    1461  0 00:15 pts/4    00:00:00 ----PS -f

ということで、シェルをログインモードで起動するには、シェルの頭に - をつけた別名で起動するか、シェルによっては -l オプションを指定して起動すればよいということです。そしてシェルはログインモードで起動されたときに、プロファイルを読み込むという実装になっています。プロファイルを読み込むのはログインシェルかどうかではなく、ログインモードで起動されたかなんです。

ターミナルソフトからのシェル起動はログイン扱いとする

TODO 結論は変わりませんが、あとで内容を大きく変更するかもしれません

ssh などで別のシステムにログインしたときは確かに「ログイン時に起動するログインシェル」ですが、現在の GUI を使った一般的なコンピュータでは「GUI でログインした後にシェルを起動」します。「ログインシェル」はログイン時に起動するシェルなので、ログインした後にターミナルソフトから起動するシェルはログインシェルとは呼べません。ログインシェルという呼び名は、現在のコンピュータユーザーにとって混乱をもたらします。

現在ではシェルはターミナルソフト(ターミナルエミュレータ)から起動するものです。ではこのシェルはプロファイルを読み込むのか?という問題があります。その答えは「ターミナルソフトがシェルをログインモードで起動していればプロファイルを読み込む」です。なので、ターミナルソフトの仕様によるわけですが、最近の(というか Windows や macOS の)ターミナルソフトの多くはシェルをログインシェルのように実行するのが一般的なようです。つまりシェルを頭に - をつけた別名で起動しているということです。ログインしてないのにログインシェルというのはおかしな話ですが、この仕様が一番合理的です。このようにしないと、プロファイルとrcファイルの使い分けが難しくなってしまいます。すべての設定をrcファイルで行うというのであればログインシェルでなくてもそんなに問題はなさそうに思えますが、使い分けたい人もいるでしょうし、システム標準の /etc/profile などが読み込まれません。

Windows Terminal や iTerm2 ではデフォルトでシェルをログインシェルとして起動していますが、もしそうなっていないターミナルソフト(GNOME Terminal など)ではシェルをログインシェルとして起動するように設定することをオススメします。これが一番ややこしくなくて良いです。ログインシェルとして起動する方法を持っていないターミナルソフトがあるのかは知りませんが、もしそのようなことができないのであれば -l オプションを指定する方法もあります。他にも色々回避策は考えられますが、おそらく考える必要はないと思うのでやめておきます。

GUI デスクトップ環境がグラフィカルログイン時に .profile を読み込むかどうかは OS (ディストリビューション)の構成次第です。読み込む場合もありますし読み込まない場合もあります。もし GUI 環境と CLI 環境の環境変数を一致させたい場合には .profile や もっと他の適切な場所で環境変数を設定したほうが良い可能性があります。しかし私は一致させる必要はないと思っています。うまく一致させられるのであればそれを利用して構いませんが、CLI 環境の環境変数は CLI 環境で(再)設定するぐらいの考えの方が混乱しないですみます。具体的かつ個人的な話をすると、私は GUI デスクトップ環境では日本語を使いますが、だからといって CLI 環境を日本語限定で使うことはしないのです。プロファイルの内容を再読み込みするために、一旦 GUI デスクトップからログアウトしたいなんて考える人はいないでしょう。コンピュータは昔よりも格段に速くなりましたが、GUI でのログインにかかる時間は、CLI でのログインにかかる時間よりも格段に遅くなりました。

非対話シェルなのにログインシェル!?

上の方で「非対話シェル and ログインシェル」のケースは普通は考えられないと書きましたが、そうなるケースも一応あります。それは ssh で別のサーバーに接続し、シェルスクリプトの内容を標準入力から渡す場合です。実行すると次のような出力が得られます。

$ cat script.sh
echo "Hello World: $-"
shopt login_shell

$ ssh server < script.sh
Pseudo-terminal will not be allocated because stdin is not a terminal.
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-112-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 ︙ (省略)

Hello World: hBs
login_shell     on

$-i が含まれていないため非対話シェルですが、ログインメッセージが出力されていることから、ログインシェルとして実行されていることがわかります。bash の場合 shopt コマンドでログインシェルであるかを調べることができますが、確かに on となっています。ちなみに ksh の場合は set -ologin_shell、zsh の場合は set -ologin で調べることができます。興味深い例ですが、一行目に「Pseudo-terminal will not be allocated because stdin is not a terminal.」が標準エラー出力に出力されていることから、あまり好ましい使い方ではないのでしょう。

補足ですが、-tt オプション(-t を 2回)を指定することでこの出力を消すことができます。しかし $-i が含まれていることから分かる通り対話シェルに変わっており、次のようにコマンドプロンプトまで出力されてしまいます。

$ cat script.sh
echo "Hello World: $-"
shopt login_shell
exit

$ ssh server < script.sh
Pseudo-terminal will not be allocated because stdin is not a terminal.
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-112-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 ︙ (省略)

koichi@server:~$ echo "Hello World: $-"
Hello World: himBHs
koichi@server:~$ exit
logout
Connection to server closed.

これはこれでリモート操作をそのままの画面をログとして保存するのに便利そうに思えましたが、一般的な使い方とは言えないでしょう。

第二部 各シェルの設定ファイルの読み込み方法

さて、ここからはそれぞれのシェルの設定ファイルの読み込み方法です。プロファイルと rc ファイルについて再確認です。

環境の設定 シェルの設定 補足: 読み込むタイミング
プロファイル 🟢 できる ❌️ できない ログイン直後の時点で読み込む
rc ファイル 🟢 できる 🟢 できる シェルを起動するたびに読み込む
  • ログイン時 ・・・ プロファイルと rc ファイルが読み込まれる
  • 非ログイン時 ・・・ rc ファイルのみが読み込まれる

ここで一つ注意が必要なのは、上記の説明は実際のシェルの動きというわけではないということです。そうではなくて、あなたがこのように読み込むように構成しなければならないのです。その方法がシェルによって異なるのが困ったところです。なんでそうなったかの歴史的な理由は第三部で説明します。ここでは実際のシェルのやり方を説明します。

補足ですが、ここからの説明では特に明記していない限り、シェルの設定ファイルを強制的に読み込むオプションや読み込まないオプションは指定していない標準の動作とします。実際のシェルはいろいろカスタマイズできるオプションを持っているのでシェルの起動モードとは関係なく、設定ファイルを読めたり読めなかったりします。

環境変数 ENV - POSIXによる標準化

POSIX は設定ファイルを読み込むための環境変数 ENV を標準化しています。ENV に rc ファイルへのパスが設定されている場合、シェルは起動時(ログイン時かどうかに関係なく)にそのファイルを読み込みます。ただし読み込むのは対話シェルのみで、シェルスクリプトを実行する非対話シェルでは読み込みません。rc ファイルの名前は自由に決めることができます。POSIX で標準化されていることもあり、多くの POSIX シェルが環境変数 ENV による設定ファイルの読み込みに対応しています。bash も POSIX 準拠モードのときに ENV を参照しており、例えば macOS の /bin/sh (bash) を起動した場合などです。経緯から言って商用 Unix では広く使われていたはずなのですが、なぜか ENV の話をほとんど聞きません。シェルによっては現役の環境変数のはずですが、失われた知識になってしまっているのでしょうか。

環境変数 ENV はプロファイルで設定することが想定されています。シェルはログインモードで起動されるとまずプロファイルを読み込みます。そしてプロファイルの中で環境設定 ENV に rc ファイルのパスを設定すると、プロファイルの読み込み後に rc ファイルが読み込まれます。プロファイルで設定された環境変数は子プロセスに引き継がれるため、新たなシェルを起動したときにも読み込みます。この仕様により、ログイン時にはプロファイルと rc ファイルが読み込まれ、非ログイン時には rc ファイルのみが読み込まれるという動作になります。

Bourne シェル用のプロファイルのファイル名が .profile であることを思い出してください。多くの POSIX シェルは互換性のために .profile を読み込みます。ただし POSIX にはプロファイルについての規定はなく、.profile というファイル名も標準化されていません。これは POSIX に準拠するシェルは .profile を実装してはいけないという意味ではなく、.profile を使ったら POSIX 準拠ではなくなるという意味でもなく、単に POSIX の標準化の範囲外だから標準規格に含めなかっただけです。POSIX としてはプロファイルをどういう方法で実装しても自由というスタンスです。POSIX で指定しないことでプロファイルの名前をシェルが自由に決めることができます。

環境変数 ENV は元々 ksh88 で発明された環境変数で、それが POSIX で標準規格として採用されました。ksh では環境変数 ENV で読み込む rc ファイルを「環境ファイル」(Environment file, ENV file) と呼んでいる(呼んでいた?)のですが、あまり一般的な呼び名ではないようです。環境変数 ENV には変数展開が行なわれた後の文字列をパスとして解釈するという奇妙な仕様があります。つまり次のような書き方でも動作します。

# 普通はこう書く
export ENV="$HOME/.rcfile"

# こう書いても動く(が、使う必要はないはず)
export ENV='$HOME/.rcfile'

# こんな事もできる
export FILENAME='.rcfile'
export ENV='$HOME/$FILENAME'

POSIXで行なわれると規定されているのは変数展開のみです。算術式展開などの他の展開やコマンド置換などについての規定はありませんが、シェルによっては行なわれる場合があります。このような奇妙な仕様がある理由は第三部で説明しますが、ksh88 の初期の頃に必要だった歴史的経緯による仕様なので、この機能が必要になることはおそらくないでしょう。

dash, FreeBSD sh, NetBSD sh, BusyBox

Debian Almquist shell (dash)、FreeBSD sh、NetBSD sh、BusyBox ash は ash (Almquist shell) の派生シェルです。これらのシェルは Bourne シェル用の .profile と POSIX に準じた環境変数 ENV による設定方法を採用しています。

1_08-sh.drawio.png

  • シェルをログインモードで起動すると .profile を読み込む
    • .profile の中で環境変数 ENV に rc ファイルのパスを設定する
  • 起動されたシェルは環境変数 ENV に設定された rc ファイルを読み込む

Ubuntu を含む Debian 系の /bin/sh は bash ではなく dash です。FreeBSD や NetBSD で自動的に作成されるユーザーの .profile には、次のようなコードがあらかじめ書かれており、標準の rc ファイル名として .shrc を使用しています。

# set ENV to a file invoked each time sh is started for interactive use.
ENV=$HOME/.shrc; export ENV

実は以前は rc ファイル名として .shinit が使われており、現在の dash や NetBSD sh のドキュメントにも .shinit が記載されています。しかし他のシェルがすべて rc を含む名前なので、.shrc に統一するのがわかりやすくて良いでしょう。ちなみに export を変数代入と分けて書いてあるのは Bourne シェルとの互換性のためです。Bourne シェルの export はエクスポート属性をつけるのみで変数代入を同時に行うことはできません。つまり、この .profile は Bourne シェルで読み込むことを想定しているわけです。

NetBSD sh 固有の注意点ですが、ENV に設定した rc ファイルは非対話シェルでも読み込まれます。この動作は POSIX 準拠ではありません。NetBSD sh のドキュメントでは対話シェルでのみ設定を行う場合の書き方として、次のようなコードが紹介されています。

case $- in *i*)
    # commands for interactive use only
    ...
esac

補足ですが、OpenBSD sh の実体は OpenBSD ksh で pdksh の派生シェルです。FreeBSD sh や NetBSD sh のベースとなった ash の派生シェルではありません。

ksh88, ksh93, ksh93u+m

ksh88 と ksh93(ksh93u+m 含む)は Bourne シェル用の .profile と POSIX に準じた環境変数 ENV による設定方法を採用しています。

【読み込みフローは sh とほぼ同じなので省略】

  • シェルをログインモードで起動すると、.profile を読み込む
    • .profile の中で環境変数 ENV に rc ファイルのパスを設定する
  • 起動されたシェルは環境変数 ENV に設定された rc ファイルを読み込む
    • (ksh93) 環境変数 ENV が未設定の場合は .kshrc を読み込む

環境変数 ENV の所で説明したように環境変数 ENV は ksh88 で発明されたものです。ksh88 の rcファイルの名前は自由ですが、「慣習として」.kshrc が使われていました。

ksh93 はその名の通り ksh88 の後継シェル(ただし完全な上位互換シェルではない)ですが、「環境変数 ENV が未設定の場合は rcファイルとして .kshrc を読み込む」という仕様が追加されています。これは 2003 年頃の ksh93 から実装された仕様で、初期の ksh93 にはありません。慣習で使われていた .kshrc がデフォルトの rc ファイル名として取り込まれた形です。言い換えると ksh93 では環境変数 ENV は不要となり .kshrc が読み込まれるということです。

NetBSD ksh, OpenBSD sh/ksh, mksh

NetBSD ksh、OpenBSD sh/ksh は pdksh の派生シェルです。pdksh は ksh88 のクローンであるため、ksh88 と同様に、Bourne シェル用の .profile と POSIX に準じた環境変数 ENV による設定方法を採用しています。

【読み込みフローは sh とほぼ同じなので省略】

  • シェルをログインモードで起動すると、.profile を読み込む
    • .profile の中で環境変数 ENV に rc ファイルのパスを設定する
  • 起動されたシェルは環境変数 ENV に設定された rc ファイルを読み込む
    • (mksh) 環境変数 ENV が未設定の場合は .mkshrc を読み込む

pdksh は ksh88 のクローンシェルで、ksh88 と似た動作を行いますが、完全な互換性があるわけではありません。

NetBSD ksh 固有の注意点ですが、NetBSD sh と同じく環境変数 ENV に設定した rc ファイルは非対話シェルでも読み込まれます。NetBSD sh は ash ベース、NetBSD ksh は pdksh ベースでルーツは異なるのですが、どちらも同じ仕様となっています。NetBSD sh と同じく対話シェルでのみ設定を行う場合の書き方として、次のようなコードを使う必要があるでしょう。

case $- in *i*)
    # commands for interactive use only
    ...
esac

OpenBSD sh の実体は OpenBSD ksh (pdksh) です。原則として同じ動作をするようですが、ドキュメントは sh と ksh で別のものが用意されており、sh 側のドキュメントには .profile や pdksh の拡張機能などが記載されていません。OpenBSD としては sh は純粋な POSIX シェルとして非対話シェル用として扱うべきであるという考え方のようです。OpenBSD の標準のログインシェルは(rootも含めて)/bin/sh ではなく /bin/ksh です。

mksh も pdksh の後継シェルですが、大幅に拡張されており別物と考えて良いシェルです。設定ファイルの読み込みの方法は、pdksh とほとんど変わりませんが、ENV が設定されていない場合に .mkshrc を読み込みます。

bash

bash は Bourne シェルの上位互換シェルであり ksh との互換性と POSIX 準拠度が高いシェルです。ksh88 の機能と ksh93 の機能のサブセットに加え、C シェルを超える対話機能を実装しています。他のシェルよりも複雑な設定ファイルの読み込み方法を採用しているため、どうしても説明することが多くなってしまいます。

1_08-bash.drawio.png

  • シェルをログインモードで起動すると、.bash_profile を読み込む
    • ただし .bash_profile がなければ .profile を読み込む
  • 起動されたシェルは .bashrc を読み込む
    • ただしログインモードの場合は .bashrc を読み込まない
    • .bashrc は設定ファイルから手動で読み込ませる必要がある)

bash の読み込みの仕組みが複雑なのは互換性と歴史的経緯によるものです。変更した方が良いという意見もあると思いますが、変更しないことによって高い互換性が保たれているのも事実なわけで簡単な話ではありません。bash は Bourne シェルとの互換性のために .profile を読み込みますが、.bash_profile があると .bash_profile のみを読み込みます。仕様としてはそこまでおかしなものではありませんが、.profile で設定を行っていたのに間違えて .bash_profile を作成してしまい .profile が読み込まれなくなったというトラブルを時々目にします。

作り込まれたプロファイルであればおそらく .bash_profile に bash の文法で設定を書き、必要な場合は Bourne シェルや他のシェルが共通で読み込む .profile に共通の文法でコードを書いて、.bash_profile から .profile を読み込むという形になるでしょう。ただしプロファイルでやることはほとんどないので、bash の文法を使ってまで書くプロファイルの例を思いつきません。

ログインモードが有効でも無効でも、シェルの設定を行うための rc ファイルは読み込むべきです。しかし bash はログインモードで起動した時に .bashrc を読み込みません。したがってログインモードで起動した場合でも .bashrc を読み込むように、ユーザーのプロファイルから .bashrc を読み込むコードを書く必要があります。このようなコードが必要なのは bash だけです。bash の設定ファイルがややこしい理由は、ユーザー側の設定ファイルでやらなければいけないことがあるからです。

C シェル由来の .bash_login.bash_logout は、基本的に使う必要はないでしょう。.bash_aliases という名前の設定ファイルを知っている人がいるかも知れません。用途としてはエイリアスや関連のシェル関数を書く場所として想定されているようです。この設定ファイルの名前は Bash 1.14 の時点で公式に examples に含まれているのですが、.bashrc から読み込むただの設定ファイルの使用例の一つにすぎず、ドキュメントにも記載はありません。ただし Debian では 2007年の Debian 4.0 でユーザーの標準の設定ファイルにコメントの形で追加され、2011 年の Debian 6.0 でコメントが外されています。Ubuntu を含む Debian 系 Linux のユーザーにとっては標準の設定ファイルのようにみなされている場合がありますが、エイリアスは .bash_aliases に書くのが正しいというわけでも推奨されているわけでもありません。ファイルが長くなってきたら、このように分離した方が良いという程度のものです。

bash がややこしいのは環境ごとに自動的に作成されるユーザーの標準の設定ファイルに違いがあるからです。単に違うだけであれば大した話ではありませんが、ユーザーの設定ファイルがシステムの設定ファイルと結びついているのが問題です。つまりDebian 系 Linux と Red Hat 系 Linux で異なる仕組みの設定ファイルが必要です。驚くべきことにこのような違いがあることを説明している記事があまりありません。ユーザーアカウントを作成した時点でシェルをある程度使いやすくするためには、単純とは言えないユーザー設定ファイルを準備しなければなりません。しかしユーザーの設定ファイルから /etc 以下にあるシステム設定ファイルを読み込んでおり、システムとユーザーの設定ファイルが完全に分離されていないため、設定ファイルを異なるシステムで使い回すにはシステムに関する知識と工夫が必要になってしまいます。

bash の設定ファイルはどうしてこんな事になってしまっているんだろうと思うことがあるのですが、おそらく bash の仕組みを単純化させすぎたために、実際のユースケースで必要なことを bash やシステム側で行えなかったのだろうと考えています。ユーザー側で工夫すればできるものの、そのための前提知識が多くなってしまいます。この記事では Debian 系 Linux と Red Hat 系 Linux の設定ファイルのみを取り上げていますが、その他のシステムでは異なる仕組みが使われているかもしれません。一般的にユーザーのホームディレクトリにある設定ファイルは自分で自由にカスタマイズして良いものですが、システムが標準で作成する .bashrc はなるべくそのままにしておいて、.bashrc から別のファイルを読み込んで、そこで自分独自の設定を行うほうが良いかもしれません。

Debian系のユーザー設定ファイル

Ubuntu を含む Debian 系 Linuxの /bin/sh は bash ではなく dash です。システムは原則として sh (dash) を使いますが、ユーザーは標準で bash を使い、sh を使うこともあるという前提でシステムが構築されているようです。

Debian 系 Linux は標準でプロファイルに .profile を使います。したがって、.bash_profile を作成したりしてしまうと .profile が読み込まれなくなってしまい、.profile に書かれた .bashrc の読み込みも行なわれなくなってしまいます。もし .bash_profile が必要なら、.bash_profile.profile または .bashrc を読み込むコードを書く必要があります。標準の .profile には次のような .bashrc 読み込みコードが書かれています。.profile は本来 Bourne シェル用の設定ファイルであることを思い出してください。.profile は Bourne シェルとの互換性を保とうとする dash やその他のシェルからも読み込まれる可能性があるため、bash だけが .bashrc を読み込むように bash チェックが行われています。

if [ "$BASH" ]; then
  if [ -f ~/.bashrc ]; then
    . ~/.bashrc
  fi
fi

この bash チェックは 2009 年の Debian 5.0(lenny)から追加されています。昔は Debian では /bin/sh に bash が使われており、それが dash に切り替わったのは 2011 年の Debian 6.0(squeeze)からです。確か計画ではもっと早くに dash に切り替えていたはずなので、bash チェックの追加は dash に対応するものなのでしょう。ちなみに Ubuntu では BASH ではなく BASH_VERSION を参照して bash チェックを行っていますが、大した違いはありません。

上の方で説明しましたが、.bashrc には、.bash_aliases を読み込むコードが含まれています。コメントに書いてあるとおり bash-doc パッケージをインストールすると該当のディレクトリに、どのような使い方を想定しているかの例がインストールされます。

# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

.bashrc には最後の方に補完スクリプトを読み込むためのコードが記載されています。ここで読み込むファイルは bash-completion パッケージをインストールすると作成されます。

# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
  if [ -f /usr/share/bash-completion/bash_completion ]; then
    . /usr/share/bash-completion/bash_completion
  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi

昔は /etc/bash_completion に補完スクリプトのコードが記述されていましたが、現在は /usr/share/bash-completion/bash_completion に移動しており、/etc/bash_completion は単にそれを呼び出すだけになっています。コメントに書いてあるように、/etc/bash.bashrc で補完スクリプトを読み込むように修正すれば、ユーザー側で読み込む必要はないのですが、標準ではユーザーの設定ファイルから読み込んでいます。補完スクリプトをシステム標準のものではなく独自のものに置き換えたい人のためにこうなっているのかもしれません。ちなみに shopt -oq posix は POSIX 準拠モードでは読み込まないようにするためのコードです。

Red Hat系のユーザー設定ファイル

Red Hat 系 Linuxでは /bin/sh は bash であり、sh は bash であるという前提でシステムが構築されているようです。

Red Hat 系 Linux では標準でプロファイルに .bash_profile を使います.bash_profile には次のような .bashrc 読み込みコードが書かれています。.bash_profile は bash 専用なので Debian がやっているような bash チェックは不要です。

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

.bashrc は Debian のよりもシンプルです。というのも冒頭付近で /etc/bashrc を読み込んでおり、システム共通の設定として初期化処理を行っているからです。ユーザーの .bashrc からシステムファイルを読み込むというのも気持ち悪いですが、システム共通の設定をすべて無視して独自のものに置き換えられるというメリットはあります。ちなみに /etc/bashrc は Debian にはありません。Debian にあるのは /etc/bash.bashrc です。

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

/etc/bashrc はブラックボックスとして、中身を見なければ何も気にすることはないのですが、中身を見てしまうと困惑するのが次のコードです。rc ファイルからプロファイル(?)を読み込んでいるのです。

    for i in /etc/profile.d/*.sh; do
        if [ -r "$i" ]; then
            if [ "$PS1" ]; then
                . "$i"
            else
                . "$i" >/dev/null
            fi
        fi
    done

正直これは混乱します。プロファイルはシェルの設定を行うものではありませんが、/etc/profile.d/ 以下にあるファイルはシェルの設定を行っており、本来であれば /etc/bashrc.d/ という名前の方がふさわしいでしょう。ただし /etc/profile.d/ 以下には拡張子 .csh のファイルもあるため共通の名前としてこのような名前になっているのでしょう。実質的にこれらはプロファイルではなく rcファイルです。ちなみに Debian にも /etc/profile.d/ は存在しますが、こちらはちゃんと /etc/profile から読み込まれています。

最近の .bashrc では次のようなコードが追加されており、.bashrc.d ディレクトリ以下にユーザー固有のrcファイルを作成できるようになっています。

# User specific aliases and functions
if [ -d ~/.bashrc.d ]; then
        for rc in ~/.bashrc.d/*; do
                if [ -f "$rc" ]; then
                        . "$rc"
                fi
        done
fi

これは .bash_aliases 相当のものですが、こちらの仕組みのほうがずっと良いですね。このコードを .bash_aliases に書けば、Debian 系 Linux でも同じ仕組みが使えます。

非対話シェルが.bashrcを読み込む!?

非対話シェル、つまりシェルスクリプトを実行するシェルでは、通常は rc ファイルは実行されません。これは当然の動作です。rc ファイルはユーザーがシェルのカスタマイズを行う場所であり、ユーザーがどのようなカスタマイズをするかは自由です。しかし一般的にシェルスクリプトを作る人はユーザーの設定のことなど考えません。シェルスクリプトはシェルのデフォルト状態で実行することを前提としているため、rc ファイルでシェルの状態を変えてしまうとシェルスクリプトが想定通りに動かない可能性があります。

一般的に非対話シェルでは rc ファイルは読み込まれません。NetBSD sh/ksh のような例外もありますが、プロファイルで行う ENV の設定が前提なのでこの話は直接関係ありません。bash も基本的には非対話シェルで .bashrc を読み込むことはないのですが、困ったことに読み込んでしまうパターンが存在します。それは標準入力がネットワークに接続している状況です。リモートコンピュータ側の .bashrc の冒頭に echo ".bashrc: $-" >&2 を書いて別のコンピュータから次のように ssh コマンド経由で(ログインせずに)コマンドを実行すると以下のような出力が得られます。この場合はログインシェルではなことに注意してください。

$ ssh server 'ps x --forest'
.bashrc: hBc
    PID TTY      STAT   TIME COMMAND
1315046 ?        S      0:00 sshd: koichi@notty
1315047 ?        Rs     0:00  \_ ps x --forest
 910450 ?        Ss     0:01 /lib/systemd/systemd --user
 910451 ?        S      0:00  \_ (sd-pam)

$- には現在のシェルオプションが代入されており、対話シェル(インタラクティブシェル)であれば、それを示す i が変数に含まれます。出力が行なわれていることから .bashrc が読み込まれているのは明らかですが、i が含まれていないので非対話シェルです。

先程の例で ps コマンドを実行したのにはもう一つ意味があり、「ps コマンドの出力に bash が含まれていないからといって、bash が実行されていないと勘違いしないようにしてください」と言うためです。sshd の子プロセスは ps で、bash はいません。しかし .bashrc の出力から bash が実行されていることは明らかです。これは bash が実行されていないわけではなく、一旦 bash が実行されたものの、ps コマンドを実行した後に何もすることがないので、bash コマンドから ps コマンドを実行する代わりに bash プロセス自体を ps プロセスへと変化させているからです。技術的に言えば fork して exec する遅い処理を最適化して exec だけを実行しています。したがって改行を加えて次にやることが残っていると騙せば、bash が実行されていることを確認できます。

$ ssh server 'ps x --forest

'
bashrc: hBc
    PID TTY      STAT   TIME COMMAND
1315730 ?        S      0:00 sshd: koichi@notty
1315731 ?        Ss     0:00  \_ bash -c ps x --forest
1315732 ?        R      0:00      \_ ps x --forest
 910450 ?        Ss     0:01 /lib/systemd/systemd --user
 910451 ?        S      0:00  \_ (sd-pam)

非対話シェルなのに .bashrc が読み込まれる理由については第三部で説明しますが man bash にはちゃんとこの仕様が書かれています。

Bash attempts to determine when it is being run with its standard input connected to a network connection, as when executed by the historical remote shell daemon, usually rshd, or the secure shell daemon sshd. If bash determines it is being run non-interactively in this fashion, it reads and executes commands from /etc/bash.bashrc and ~/.bashrc, if these files exist and are readable. It will not do this if invoked as sh. The --norc option may be used to inhibit this behavior, and the --rcfile option may be used to force another file to be read, but neither rshd nor sshd generally invoke the shell with those options or allow them to be specified.

日本語版のドキュメントからの引用(少し内容が違う、補足の部分)

bash は、リモートシェルデーモン rshd やセキュアシェルデーモン sshd によって実行された場合など、標準入力がネットワーク接続に接続された状態で実行されたかどうかを調べます。この方法によって実行されていると bash が判断した場合、(補足: /etc/bash.bashrc や) ~/.bashrc が存在し、かつ読み込み可能であれば、 bash はコマンドをこのファイルから読み込んで実行します。 sh として呼び出された場合には、この動作は行いません。--norc オプションを使えばこの動作を禁止できますし、 --rcfile オプションを使えばほかのファイルを読ませるようにもできます。 しかし一般的には rshd (補足: や sshd)は これらのオプションを付けてシェルを起動しませんし、 指定もできないようになっています。

この問題が発生するのは(rsh コマンドや)ssh コマンド経由で任意のコマンドを実行したりする他に、内部で ssh コマンドを実行している rsync コマンドや scp コマンドでも発生します。軽く調べてみましたが ssh 経由でコマンドを実行したときに .bashrc を読み込まなくする、一般的な方法はなさそうです。

こちらの記事「Bash: .bashrcと.bash_profileの違いを今度こそ理解する」では、.bashrc でなにか出力を行うと rsync の動作がおかしくなるため「~/.bashrc で何も出力してはならない」と結論付けていますが、私の結論はそもそも「非対話シェルで .bashrc を実行してはならない」です。Debian 系 Linux の標準の ~/.bashrc では冒頭に以下のコードが記述されており、非対話シェルで .bashrc を読み込んだときにすぐに終了するようになっています(同様に /etc/bash.bashrc もすぐに終了するようになっています)。このコードがある限り対話シェルの .bashrc で出力を行っても問題ないはずです。このコードは Red Hat 系 Linux の標準の設定ファイルにはないようなので追加しておくとよいでしょう(もちろん /etc/bashrc を読み込む前に追加です)。

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

ちなみに上記のコードは、もっと短く書くことができます。

# 非対話シェルの場合 .bashrc を終了する
#   $- に i が含まれていないなら return
[[ $- = *i* ]] || return 0

# POSIX に準拠した書き方をしたい場合
[ -z "${-%%*i*}" ] || return 0

ssh で非対話シェルで実行したときに .bashrc が読み込まれるという仕様は便利な点もあって、ここで環境変数を設定できることです。その場合は先程の非対話シェルの場合に .bashrc を終了するよりも前に書きます。

# .bashrc

# ここで環境変数を設定できる
export PATH=$HOME/bin:$PATH

# 非対話シェルの場合 .bashrc を終了する
[[ $- = *i* ]] || return

しかし、このようなことができるのは ログインシェルが bash の場合だけで、ログインシェルが sh や zsh の場合には機能しません。このようなことがしたいのであれば、ssh server bin/script.sh のように絶対パスか相対パスで実行するようにして、必要ならばシェルスクリプトの中で環境変数を設定した方が良いでしょう。他にも ssh server '. .envfile; script.sh のような呼び出し方に変更する方法も考えられます。以下はこの話に関するその他の関連記事です。

zsh

zsh は ksh と C シェルを参考にしながらも、互換性よりも独自路線を選び多くの拡張機能を備えたシェルです。bash と同じように ksh88 と ksh93 のサブセットを実装しています。zsh の設定方法は、ksh93 や bash とも違い固定のファイル名を読み込むシンプルな方法です。

1_08-zsh.drawio.png

  • シェルをログインモードで起動すると .zprofile を読み込む
  • 起動されたシェルは .zshrc を読み込む

zsh は bash とは異なりデフォルトでは bash や ksh と互換性が低く POSIX にも準拠していません。ただし、互換性とPOSIX 準拠度を向上させる sh または ksh エミュレーションモードを持っています。標準では .profile や環境変数 ENV を参照しませんが、sh または ksh エミュレーションモードを使えば .profile を読み込み、環境変数 ENV を参照します。zsh では bash とは異なり .zprofile から .zshrc を読み込むコードを書く必要はありません。ログインモードで起動した場合も、.zshrc は必ず読み込まれます

C シェル由来の .zlogin.zlogout は、基本的に使う必要はないでしょう。.zshenv は zsh で書かれたシェルスクリプトを起動したときに必ず読み込まれるファイルです(知らずに使っていませんか?)。対話シェルでも読み込まれますが、対話シェルなら .zshrc を使えば十分です。.zshenv は非対話シェルでシェルスクリプトの実行前に強制的に環境変数を変更するという変なことをするときぐらいしか使い道がありません。bash の「非対話シェルが.bashrcを読み込む!?」の話と同じような用途で使えますが、通常は使う必要がなく、極めて特殊な用途で使うものです。

yash

yash はまじかんと(渡邊裕貴氏)によって開発されている、純日本製の POSIX 準拠シェルです。現在の 2 系は 2008 年頃から開発されており、POSIX の標準化作業でも POSIX シェルの実装の一つとして認知されており十分な実績があるシェルです。

【読み込みフローは zsh とほぼ同じなので省略】

  • シェルをログインモードで起動すると .yash_profile を読み込む
  • 起動されたシェルは .yashrc を読み込む

yash は .profile を読み込むことはありません。代わりに .yash_profile を読み込みます。また、環境変数 ENV を参照せずに .yashrc を読み込みます。POSIX 準拠モード(正確には POSIXly-correct モード)では、.yash_profile.yashrc は読み込まず、ENV を参照して rc ファイルを決定します。

ところでこの動作には少し気になる点があります。それは ENV はどこで設定すればよいのだろう?ということです。ENV を参照するのは POSIXly-correct モードの場合だけです。POSIXly-correct にする方法はいくつかありますが、その一つは /bin/sh として使う場合です。しかし yash は .profile を読み込みませんし、POSIXly-correct モードでは .yash_profile.yashrc を読み込みません。ENV を設定するルートは他にもありますが、yash を /bin/sh としてログインシェルとして使う場合、プロファイルも rc ファイルも読み込むことができないことになります。これが意図した動作なのか、考慮漏れなのか、私の勘違いなのかはわかりません。

.yashrc が存在しない場合、使いやすく設定されたサンプルの .yashrc ファイル(おそらく /usr/share/yash/initialization/sample にある)が読み込まれます。カスタマイズする場合はこのファイルをホームディレクトリにコピーして使うとよいでしょう。

第三部 主要なシェルの設定ファイルの歴史

さて、ここからは楽しい歴史のお話です。いわゆる「おまけが本編」です。ここではシェルの設定ファイルにおいて説明の必要があると感じたシェルを取り上げています。

設定ファイルなし (Thompson sh)

1971年、Version 1 Unix が公式にリリースされました。公式リリースといっても社内リリースです。Thompson シェルもその時にリリースされたことになっていますが、Unix が誕生した 1969 年には Thompson シェルは(ある程度の)形になっていたはずです。Unix シェルがないと Unix は使えませんし、Thompson も1969 年の夏にシェルを開発したと言っています。この頃の Unix は研究開発用の Unix として現在は Reaearch Unix と呼ばれています。

最初の Thompson シェルには設定ファイルはありませんでした。最初のバージョンだから機能は最低限だったというのもあるでしょうし、当時に Unix が動いたハードウェア環境は限られていますし、環境変数やシェル変数もなかったので設定ファイルの必要性も少なかったのでしょう。

旧 .profile (PWB sh)

シェルの最初の設定ファイルである .profile は PWB シェルで登場しました。PWB シェルは、Research Unix とは少し異なる実用プログラマ向けの PWB/UNIX 用のシェルとして誕生しました。PWB とは Programmer's Workbench の略です。余談ですが PWB/UNIX の成果(PWB/UNIX で作られたコマンドなど)は後に商用 Unix である System III や System V に組み込まれます。

PWB/UNIX は Thompson シェルを改良したシェルで、シェルにフロー制御などのプログラミング機能が組み込まれました。その機能の一つがシェル変数です。シェル変数の変数名は 1 文字しか使えず、そのいくつかは特殊な用途に使われていましたが、現在の環境変数 PATH に相当する p 変数などがありました。p 変数を使うことでコマンド検索のパスを指定することが可能になり、ユーザーが自分専用のコマンド群を使うためのカスタマイズが必要になります。その他 PWB/UNIX は外部に販売されてため、各端末用の設定なども必要になるでしょう。ここで設定ファイルの必要性が生まれます。

PWB/UNIX の .profile は現在と同じようにログインしたときのみ読み込まれました。おそらくログインした時に読み込ませると言うよりも、シェルスクリプトを実行するときに、時間がかかる初期化処理を読み込ませたくなかったというのが理由だと思われます。当時の対話シェルはログインするときのみに必要で、別のシェルを起動したり他のプログラムからシェルを起動するという使い方もまだ一般的ではなかったでしょう。PWB シェルが .profile を読み込む仕組みは、シェルが - という名前で起動されたときです。-sh ではなく - です。他のシェルと区別する必要性を想定していなかったのでしょう。今と仕様は少し違いますが、このときにプログラム名の - でログインシェルを区別する方法が誕生しました。

.profile (Bourne sh)

Bourne シェルは Reaearch Unix 用のシェルとして Thompson シェルの後継、そして PWB シェルの機能を参考に、新しく再設計されて作られたシェルです。Bourne シェルは .profile の読み込みに対応していましたが、Thompson シェルまたは PWB シェルとシェル言語の互換性はないため、PWB シェルの .profile は読み込めません。PWB シェルは PWB/UNIX 独自の機能を必要としていたようで、どうやら Research Unix では PWB シェルは動かなかったようです。したがって互換性を考慮する必要もなかったのでしょう。Bourne シェルは .profile という設定ファイルの名前だけを再利用しました。

Bourne シェルは主にプログラミング言語としてシェルの機能を強化したシェルです。対話シェルの機能は不足しており、初期の実装ではシェル関数もありませんでした。その一方で環境変数の機能が Unix とともに追加されています。つまりログイン時の .profile は必要でしたが、シェルの設定を行う必要性はほとんどなかったため、rc ファイルの必要性をあまり感じなかったのでしょう。

Bourne シェルでは .profile を読み込む仕組みが、現在と同じシェルの名前が「- で始まる名前」に改められました。

.cshrc (csh)

Bourne シェルとほぼ同時期に作られていたのが C シェルです。C シェルは Thompson シェルを大幅に改良して作られたシェルで、PWB シェルのアイデア(構文など)も一部取り込まれているようです。C シェルは Bourne シェルとは異なり対話シェルの機能を大きく強化しました。シェル関数はありませんでしたが、エイリアスなどのシェルの機能を持っています。C シェルは BSD Unix 用に作られたシェルですが、標準の Unix シェルである Bourne シェルに比べると「追加のシェル」という立場です。sh から csh を呼び出すこともあるでしょうし、テキストエディタなどの中からシェルを起動することもあります。つまりログイン時以外のシェルの起動でシェルの設定を行う必要性がでてきました。

シェル言語の文法が異なるため C シェルは .profile を読み込むことはできません。そこで別のシェル設定ファイルが必要になりました。それが .login.cshrc.logout です。ファイル名に「rc」を付ける慣習は、C シェルで誕生したものです。

C シェルではログイン時に .cshrc が読み込まれた後に .login が読み込まれます。ログイン時でない場合は .cshrc のみが読み込まれます。.cshrc は非対話シェルでも読み込まれます。そのため、FreeBSD での標準の .cshrc では対話シェルかどうかを判定してシェルの設定を行うようになっています。

setenv	EDITOR	vi
setenv	PAGER	less

if ($?prompt) then
	# An interactive shell -- set some stuff up
	set prompt = "%N@%m:%~ %# "
	set promptchars = "%#"fi

対話シェルでも非対話シェルでも、環境変数の設定を rc ファイルで行っている。という処理の流れは、ssh 経由でコマンドを実行する時の書き方に近いものを感じます。

C シェルの後継の tcsh では .tcshrc ファイルがあればそれが読み込まれ、なければ .cshrc が読み込まれます。

環境変数ENV (ksh88、pdksh)

KornShell は Bourne シェルの後継として、Bourne シェルのソースコードを元に作られたシェルです。一応 KornShell は実装ではなく仕様ということになっています。初期の KornShell の実装には ksh83 や ksh86 がありますが、これらは限られた範囲にしか配布されておらず、ここでは ksh88 を最初の公式の KornShell 実装とみなします。KornShell の仕様は公開されていましたが、ksh88 の実装はクローズドソースでした。そこで KornShell の仕様(本として出版されています)を元に実装されたパブリックドメイン版の KornShell が pdksh (Public Domain KornShell) です。pdksh は ksh88 と同等の機能を持つシェルですが、互換性は完全ではありません。NetBSD ksh や OpenBSD ksh は pdksh と同等の機能を持つ派生シェルです。

初期の ksh88 では rc ファイルは非対話シェルでも読み込まれていました。C シェルもそうですが、この頃は非対話シェルでは読み込まないという考え方がなかったのでしょう。ただし ksh93 では対話シェルでのみ rc ファイルを読み込む仕様に変更されています。

KornShell は Bourne シェルの言語機能を強化し、C シェルの対話機能を取り入れたシェルです。したがって C シェル相当の設定ファイルの仕組みが必要になります。KornShell で追加されたシェル言語の機能には「強化された変数展開」と「算術式展開」と「配列」があります。シェルの設定ファイルの話に関係するので、これらの機能について簡単に説明します。まず強化された変数展開とは次のようなものです。これらは現在の POSIX シェルでも標準化されています。

filepath="/tmp/archive.tar.gz"

# %の後ろのシェルパターンを削除する(最短マッチ)
echo "${filepath%.*}" # => /tmp/archive.tar

# %%の後ろのシェルパターンを削除する(最長マッチ)
echo "${filepath%%.*}" # => /tmp/archive

KornShell は配列の機能も持っています。ただし bash などとは初期化の構文が違います。配列は POSIX では標準化されていません。

# 配列への代入 ary=(foo bar baz) のような書き方はできない
set -A ary foo bar baz

# 添字は 0 から
echo ${ary[1]} # => bar

# 添字を省略すると、添字 0 として扱われる
ary="test"
echo ${ary[0]} # => test
echo ${ary[1]} # => bar

KornShell は算術式展開の機能も持っており整数の計算が行えます。この機能は POSIX でも標準化されています。

echo $((1 + 2)) # => 3

# 当然変数も使える
var=100
echo $((var + 23)) # => 123

算術式は配列の添字でも使えます。配列自体が POSIX で標準化されていないので、この使い方は POSIX で標準化されていませんが、配列に対応している POSIX シェルでは配列の添字に算術式が使えます。

set -A ary foo bar baz
var=1
echo ${ary[var + 1]} # => baz

さて、これらの機能を組み合わせて何ができるでしょうか?

すでに説明している通り、KornShell (ksh88) は、環境変数 ENV を使った設定ファイルの読み込み手法を採用しています。そして ksh88 版の KornShell の公式の仕様本である「The KornShell Command and Programming Language」の P78 で、環境変数 ENV を使った設定ファイルの読み込み方法の例として次のようなものが紹介されています。

FILE=$HOME/.envfile; export FILE
# The subscript below evaluate to 0 when interactive.
ENV='${FILE[(_$-=0)+(_=1)-_${-%%*i*}]}'

はい、みなさんご一緒に。

「よめねーよ!」

まあ、次のようにちゃんと説明が書いてあるのでやりたいことはわかるのですが、肝心のコードが読めません。

ENVIRONMENT FILE

Whenever ksh begins execution, it expands the ENV variable, and executes a script by this name if it exists. This file is called your environment file. Use this file to:

  • Define aliases and functions that apply for interactive use only; or for both interactive use and scripts invoked from ksh.
  • Set default options that you want to apply to all ksh invocations.
  • Set variables that you want to apply to the current ksh invocation.

You may want certain commands in your environment file to be in effect only for interactive shells. The - parameter contains an i in its value when ksh runs interactively. You can check $- within the environment file.

If all the commands are for interactive use, you can specify a value for ENV that expands to Null when ksh is not interactive. The following example shows how to define a subscript that will evaluate to 0 or 1, depending on the value of parameter -. Use this to define a value for ENV that will expand to file only when the interactive option is on.

コードの意味をひも解いていきましょう。

FILE=$HOME/.envfile; export FILE
ENV='${FILE[(_$-=0)+(_=1)-_${-%%*i*}]}'

# FILE配列の添字が最終的に 0 か 1 になる
ENV='${FILE[  (_$- = 0) + (_ = 1) - _${-%%*i*}  ]}'

# $- にはシェルオプションが代入されている(例 himBHs)
# つまり _$- は _himBHs のような変数名に展開される
#
# _${-%%*i*} は $- に i が含まれていればパターンにマッチするので
# 変数名 _ となり、含まれていなければ _hmBHs のような変数名になる

# $- に i が含まれている場合(対話シェル)
ENV='${FILE[  (_himBHs = 0) + (_ = 1) - _  ]}' # 0+1-1 = 0

# $- に i が含まれていない場合(非対話シェルモード)
ENV='${FILE[  (_hmBHs = 0) + (_ = 1) - _hmBHs  ]}' # 0+1-0 = 1

ということで、対話シェルでは FILE[0] を参照するので $HOME/.envfile を読み込み、非対話シェルでは FILE[1] を参照するので何も読み込まないという動作になります。先に書いたように 初期の ksh88 では、環境変数 ENV に設定したファイルは対話シェルと非対話シェルの両方で読み込むという仕様でした。そのためこのような巧妙な方法を使って、対話シェルだけで rc ファイルを読み込む方法が想定されていました。だから、環境変数 ENV は変数展開が行われた後のパスを読み込むという仕様があるのです。

まあ、わかるんですけどね。この方法は柔軟で頑張れば対話、非対話の違いだけでなくさまざまなシェルオプションごとに異なる設定ファイルを読み込ませることもできるでしょう。応用の幅は広いです。また、bashがやっているようにシェルの設定ファイルの中で対話シェルでなければ終了するというコードに比べて、設定ファイルを読み込まない分パフォーマンスは上です。しかしですね。これは無理。読めません。

だからか、この仕様は ksh93 と ksh88 の 12/28/93 の修正で、環境変数 ENV の設定ファイルは非対話シェルでは読み込まないように変更になっています。ksh93 版の新しい KornShell の公式な仕様である「The New KornShell Command And Programming Language」の P88 では、あの長ったらしい説明文がばっさり削除されており 11/16/88 では 非対話シェルでも呼び出されていたと一言記述されています。

NetBSD ksh、OpenBSD sh/ksh、mksh はいずれも pdksh の派生シェルです。pdksh は ksh88 のクローンシェルであるため環境変数 ENV は非対話シェルでも読み込まれていました。NetBSD ksh は今も変わらずですが、OpenBSD sh/ksh は 2007 年に、mksh は 2006 年に、非対話シェルでは読み込まないように修正されています。

POSIX における設定ファイルの扱い

POSIX では環境変数 ENV が標準化されています。ただし UP (User Portability Utilities option) なので実装必須となっているわけではありません。どうやら POSIX.2-1992 の時点では環境変数 ENV の読み込みを対話シェル限定とは指定しておらず(ksh88 の修正前なのでできない)、2000年頃の POSIX.2b の改定で歴史的なシェルの動作は対話シェルのみで読み込むように変わっているとして変更されたようです。

POSIX で .profile は標準化されていませんが、その理由は「ログイン」についての規定がないからです。ログインシェルという用語は登場しますが、それが何であるかは定義されていません。なぜログインシェルがないのかと言うと、ログインプログラム(login コマンド)自体が標準規格の中に存在しないからです。POSIX は Unix を元に作られましたが、Windows などを含むさまざまな OS に実装可能な OS のインターフェスとして作られています。ログインプロセスはシステム毎に異なっているのが当たり前なので POSIX の標準化の対象外です。POSIX の目的は(全く異なる OS に)アプリケーションを移植しやすいように OS のインターフェースを標準化することであって、Unix や任意の OS の規格を作ることではありません。

ログインについての規定がないことが、.profile が標準化されていない理由ですが、個人的にはログインの定義を未指定にしたまま標準化できたのではないかと思っています。つまり「シェルをログインモードで起動すると .profile を読み込むが、誰がログインモードで起動するかについては具体的な指定はない」というような感じの内容です。実際にシェルをログインモードで起動するのはログインプログラムだけではなく、ターミナルソフトもログインモードで起動します。今やログインモードは単なるシェルの起動モードの一つに過ぎません。ただし POSIX で .profile を必ず読むように指定すると、現在 .profile を(デフォルトで)読まないシェルが、POSIX に準拠するために読み込まなければならなくなり、読み込まれることを想定せずに書かれた .profile を読み込んでしまうとシェルの動作おかしくなる可能性があるので、今更このような仕様を強要することはないでしょう。現状はプロファイルの実装に関して制限のない自由な状態なわけで、自由を制限するようなことを POSIX が行うのは考えられません。

.shinit, .shrc (ash、dash)

FreeBSD sh と NetBSD sh は ash (Almquist Shell)という名前のシェルがベースになっています。Debian 系 Linux で使われている dash は NetBSD sh のフォークで、BusyBox ash は dash のフォークです。いずれにしろ大元の祖先は ash で、BSD Unix でライセンスの問題で使えなくなった Bourne シェルの置き換えとして作られました。1989年5月にニュースグループに投稿され、1991 年のBSD 4.3-Net/2 に含まれて配布されました(参考)。

ash には最初、.profile はありましたが環境変数 ENV はありませんでした。これは ash が Bourne シェルの置き換えとして作られたことを考えれば理解できます。ksh88 の ENV と近い機能を提供する環境変数 SHINIT がありましたが、ログイン時(と非対話シェル)では読み込まれませんでした。4.4BSD Lite2 では ENV に変更されましたが、非対話シェルでも読み込まれる修正前の初期の ksh88 と同じ仕様です。つまり ksh88 との互換性のために実装されたということなのでしょう。そのため $- を参照して対話シェルのみで設定を行う方法が紹介されています。このときに .shinit という設定ファイル名が登場しています。元は環境変数 SHINIT だったから .shinit ということなのでしょう。

  • 1991: 4.4BSD Lite1 SHINIT (ログインシェルと非対話シェルでは読み込まれない)
  • 1992: 4.4BSD-Alpha POSIX準拠作業の開始(POSIX.2 の 標準化は 1992 年)
  • 1995: 4.4BSD Lite2 ENV (ただし非対話シェルでも読み込まれる)

1993 年に BSD Unix から派生して、FreeBSD と NetBSD の 2 つの BSD 系 Unix が誕生します。まだ BSD Unix の ash が ENV に切り替わる前です。1993 年の FreeBSD 1.0 の時点ではまだ SHINIT が使用されていました。1994 年の FreeBSD 2.0 から ENV に切り替わり、1997 年の FreeBSD 2.2.5 から ENV は対話シェルでのみ読み込まれるようになります。この仕様変更に関して man sh では変更した理由を次のように書いています。

Unlike older versions of sh the ENV script is only sourced on invocation of interactive shells. This closes a well-known, and sometimes easily exploitable security hole related to poorly thought out ENV scripts.

古いバージョンの sh とは異なり、ENV スクリプトは対話シェルの起動時にのみ読み込まれます。これにより、よく知られ、そして容易に悪用される不適切な設計の ENV スクリプトに関連するセキュリティホールが閉じられます。

少なくとも FreeBSD では、rc ファイルを非対話シェルで読み込むのは、セキュリティ上の問題があると感じていたということです。これに関しては「確かにその通りだけど、そもそもそんな rc ファイルを書くユーザーが悪い」という考え方もできるでしょう。

NetBSD も同様に、最初は SHINIT を使用していました。そしてすぐに ENV に置き換わりました。ただし FreeBSD のように対話シェルのみで読み込まれるように変更しなかったようです。つまり初期の修正前の ksh88 との互換性の動きのままということです。修正されていないことについて少し調べ、比較的最近 (2024/09) に関連しそうな話題を見つけました。以下を読む限り意図的にそのままにしているような印象を受けましたが、実際にどうなのかはよくわかりませんでした。

.bash_profile, .bashrc (bash)

bash は ksh88 と ksh93 のサブセットを実装しており、実装された機能の範囲であれば bash は ksh と互換性が高いシェルです。bash が独自の設定ファイル読み込み方法にした理由は、ksh88 の環境変数 ENV の仕組みが複雑だと考えたからです。当時はまだ ksh88 の ENV は対話シェルと非対話シェルの両方で読み込まれており、その仕様に上手く対応するのは面倒でした。このときの方針の決定については、bash のリポジトリの doc ディレクトリ に含まれている、初期の bash を紹介した以下の文書で明らかになっています。

  • article.pdf (1994年頃 bash-1.14.1 が現役の頃に書かれた)
  • rose94.pdf (1994年頃 bash 2.0がでる直前に書かれた)

4.1. Startup Files

Bash executes startup files differently than other shells. The Bash behavior is a compromise between the csh principle of startup files with fixed names executed for each shell and the sh "minimalist" behavior. An interactive instance of Bash started as a login shell reads and executes ~/.bash_profile (the file .bash_profile in the user's home directory), if it exists. An interactive non-login shell reads and executes ~/.bashrc. A non-interactive shell (one begun to execute a shell script, for example) reads no fixed startup file, but uses the value of the variable $ENV, if set, as the name of a startup file. The ksh practice of reading $ENV for every shell, with the accompanying difficulty of defining the proper variables and functions for interactive and non-interactive shells or having the file read only for interactive shells, was considered too complex. Ease of use won out here. Interestingly, the next release of ksh will change to reading $ENV only for interactive shells.

上記の article.pdf の文章より bash は C シェルを参考にして .bash_profile.bashrc のような決め打ちの設定ファイル名を採用し、最小限のシェルの動作として、ログイン時は .bash_profile のみを、非ログイン時は .bashrc のみを読み込むようにした事がわかります。C シェルではログイン時は .login.cshrc の両方を読み込みますが、Bourne シェルは .profile しか読み込みません。このようなそれまでの他のシェルの動作と同じような使い方ができるようにしたのでしょう。シェルの動作としてはシンプルですが、実用という点からみると.bash_profile からわざわざ .bashrc を読み込まなければならない不便さとなってしまいました。環境変数 ENV は bash では元々非対話シェルで読み込むための環境変数でしたが、現在では環境変数 ENV は POSIX に準拠した仕様で、代わりに環境変数 BASH_ENV が非対話シェルで読み込ませるためのものとして使用されています。

Re: why ~/.bashrc in ssh or scp??」のメールではログインシェルが .bashrc を自動的に読み込まない理由について、現在のメンテナである Chet Ramey は「自分が bash に関わる前の話(オリジナルの開発者は Brian Fox)だからわからないが、Bourne シェルと C シェルの動作の折衷案だと思っていた」と述べています。つまり、設定ファイルを書き方次第で Bourne シェルの動作にすることもできるし、C シェルの動作にすることもできるようにしたのだろうということです。

また、ssh 経由で非対話シェルを実行したときに .bashrc を読み込む理由について、対話シェルと同じ環境変数 PATH を使えるようにする(当時のユーザーが .bashrc ファイルを読み込むことを望んだ)ためだと述べています。つまり設定ファイルを読み込まなければ、環境変数を設定する場所がないためです。ssh でログインしたかのようにコマンドを実行するのだから、.bashrc ではなく .profile を読み込むほうが適切な気もしますが、おそらくログイン時に読み込まれる .profile にはログインメッセージを出力するという一般的な使い方が存在していたためにできなかったのでしょう。それに .profile から .bashrc を読むのであれば違いはありません。この話については以下のツリーも参考になるでしょう。他にも詳しく調べればなにか見つかるかもしれません。

現在の使い方に当てはめればもっと良い方法があったのではないか?と思うのは、今だから言えることです。適当に考えて実装したわけではなく、当時はそれが良いと考えられていたのです。

.zprofile, .zshrc (zsh)

zsh のことを勘違いしている人を見かけますが、zsh は bash の後継として開発が始まったシェルではありません。bash の上位互換ではないので bash で動くものが zsh でも動くわけではありません。bash と zsh は、どちらも ksh を参考にして作られているため、似ている所がありますが違うシェルです。bash の最初のリリースは 1989 年6月、zsh のリリースは 1990年12月で、同じ時期に開発が行われています。zsh は元々 Commodore Amiga 用 の csh のサブセットとなる予定だったものが、大幅に機能が膨らみ、途中から ksh と tcsh の中間となることが目標となりました。そのことはメーリングリストで公開された「zsh - a ksh/tcsh-like shell」や、zsh 1.0 のソースコード に含まれているドキュメントに書かれています。

This shell was "developed" by me, Paul Falstad, a sophomore at Princeton University. I borrowed heavily from ksh, bash, tcsh, sh, and csh, as well as adding a few (IMHO) useful features. zsh was at first intended to be a subset of csh for the Commodore Amiga, but the project sort of ballooned; now I want it to be a cross between ksh and tcsh. It should be a powerful "command and programming language" that is well-designed and logical (like ksh), but it should also be built for humans (like tcsh), with all the neat features like spell checking, login/logout watching and termcap support that are probably too weird to make it into an AT&T product. This version of the shell has fallen far short of that goal; I just wanted to release something, because this is obviously going to be an extremely long project.

zsh の開発では bash も参考にしていますが、ksh や bash との互換性を第一に考えているわけではなく、優れたシェルを作ることを目標にしています。zsh のシェル言語は C シェルの影響も受けており、例えば zsh の配列の添字が 1 からなのは C シェルの影響でしょう。ksh と bash の添字は 0 からなので意図的に変更していることは明らかです。ちなみに C 言語の配列の添字は 0 からなので、C シェルよりも ksh や bash のほうが C 言語に近いシェルといえます。zsh は sh または ksh のエミュレーションモードを持っており、sh 風や ksh 風の動作に変更できます。bash エミュレーションモードはありません。なぜなら bash の動作は ksh に似ているため、オリジナルと言える ksh エミュレーションがあれば十分だからです。

C シェルの影響は設定ファイルにも現れています。zsh 1.0 の頃の設定ファイルは .zshrc.zlogin.zlogout はありましたが、.zprofile はありませんでした。これは C シェルの設定ファイルである .cshrc.login.logout に対応しています。.zprofilezsh 2.0 で登場しました。C シェルでは .cshrc の後で .login が読み込まれるため、同じように .zshrc の後で .zlogin が読み込まれます。それとは逆に .zprofile.zshrc よりも先に読み込まれます。細かく見ると読み込み順の違いでややこしいですが、そもそも C シェルユーザーのための仕様なので、気にしなければ物事はシンプルになります。

設定ファイルの歴史のまとめ

設定ファイルはまず、.profile から誕生しました。プロファイルは環境変数や端末の設定などを行うためにログイン時に読み込まれるファイルでした。C シェルで対話シェルの機能が増えたとき、シェルの設定を行うための rc ファイル、すなわち .cshrc 、そして .login.logout が誕生しました。.cshrc は対話シェルと非対話シェルの両方で読み込まれました。

C シェルの機能を取り込んだ ksh88 によって、rc ファイルを読み込むための環境変数 ENV が誕生しました。rc ファイルは対話シェルと非対話シェルの両方で読み込まれ、それを防ぐためには ksh88 の機能を駆使した複雑な値を ENV に設定する必要がありました。この ENV の仕様は ash や pdksh にもコピーされましたが、ash や pdksh では複雑な値を設定するのではなく rc ファイルの中で対話シェルと非対話シェルを区別する方法が提案されていました。高度な機能を持たない ash では ksh88 と同じことができなかったのも理由の一つです。

1993 年に ksh88 と ksh93 の環境変数 ENV の仕様が変更になり rc ファイルは対話シェルでのみ読み込まれるように制限されました。POSIX でもその仕様が採用され標準化されることになります。多くの ash と pdksh の派生シェルも(NetBSD を除き)対話シェルでのみ rc ファイルを読み込むように修正されていきました。環境変数 ENV を使う仕組みは現在も使われています。

初期の ksh88 の環境変数 ENV の仕様が複雑すぎると考えていた bash は ksh88 と C シェルの仕様を合わせたような、独自の設定ファイルを作りました。また、同じように zsh も C シェルを参考にしたかのような独自の設定ファイルを作りました。これらは ENV とは異なり、設定ファイルの名前が固定です。そして ksh93 や mksh でも ENV が設定されていないときに、それぞれ異なる固定のファイル名を読み込むようになりました。

このようにシェルの rc ファイルは、最初対話シェルと非対話シェルの両方で読み込まれていましたが、特殊な例を除き、対話シェルだけで読み込まれるように変わりました。また、設定ファイル名も環境変数 ENV を使う自由だったものが、シェルごとに専用のファイルを使うように変化しています。シェルの設定を行わないプロファイルとは異なり、シェルの設定を行う rc ファイルはシェルごとに違う内容になるので、共通の環境変数 ENV を使う方式ではシェルごとに設定ファイルを分けづらいからでしょう。シェルの設定ファイルは、より簡易的で汎用的なものから、より実用に適したものへと変化していることが、歴史をたどることでわかります。

さいごに

シェル(対話シェル)を使いこなすうえでシェルの設定ファイルは重要です。しかしながら、bash や zsh の設定ファイルの話を、とりあえず「プロファイルに全部書けば良い(間違い)」や「rc ファイルに全部書いておけば良い」みたいな話で終わらせてしまい、考え方について書いてる記事はほとんど見当たりません。考え方がわからないから読み込み順を調べてみるも、結局ややこしいことがわかっただけで終わりです。設定ファイルに関するノウハウや歴史が継承されていないように思えます。シェルごとに細かい動作は違いますが、基本はプロファイルと rc ファイルだけです。この記事で設定ファイルに対する理解が広まれば幸いです。

104
123
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
104
123

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?