はじめに
毎度おなじみ、いつものシェルの歴史研究家(自称)です。今回は誰もが雰囲気で使っているはずのシェルの設定ファイルのお話です。シェルの設定ファイルって、いっぱいあってなんだかよくわからないですよね? どういうファイルがあってどういう順番で読み込まれるのか、どこに何を書けばいいのか? この記事はそのシェルの設定ファイルを完全に理解できる・・・ことを目指す記事です。しかも全 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 ファイルの読み込むタイミング、「シェルを起動するたびに読み込む」の意味ですが、これはログインした後にコマンドラインから bash
や zsh
と入力して別のシェルに切り替えたり、テキストエディタの中からシェルを実行することです。そのようなことをしない人は全くしないかもしれませんが、そういう時の話です。シェルの設定はシェルごとに違うため、rc ファイルは原則としてシェルごとに異なる設定ファイルが必要です。ただしエイリアス設定など共有できる部分もあります。
なぜプロファイルとrcファイルの2つの設定ファイルがあるのかですが、最初はプロファイルしか設定ファイルがなかったからです。しかしプロファイルの読み込みタイミングではシェルの設定ができなかったので、後から作られたのが rc ファイルです。この話は第三部の歴史の話で詳しく説明します。
プロファイルとrcファイルの読み込み順
プロファイルはログイン時(ログインシェル)でしか読み込まれません。ターミナルソフトからシェルを起動したときもログイン時とします。正確にはターミナルソフトの設定によるのですが、実用という観点からログインした時と同じように振る舞うべきであり、最近のターミナルソフトはデフォルトでそういう挙動になっているのが多いはずです。rc ファイルは(対話シェルなら)常に読み込まれます。ログイン時でもログイン時じゃなくて読み込まれます。それはそうです。ログイン時でもログイン時じゃなくてもシェルの設定は必要だからです。ここでこの説明が間違っていると思った人は、rc ファイルは常に読み込むものだと騙されてください。些細な話としてプロファイルは rc ファイルよりも先に読み込まれます。読み込み順について知る必要があるのはこれだけです。知る必要がないものを省けば、他の詳しすぎる図と比べてここまで単純化できます。
実際のシェルでは、シェルによって 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ファイルで環境変数 EDITOR
を vim
に設定するコードを書いていた場合、次のようなことになってしまいます。
$ 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
コマンドが対応していれば良いと思うのですが、残念ながらそんな便利な機能はありません。
注意: イメージです。これは動きません。
$ env -a -bash bash
↑でやってることと同じことを↓でできる
$ (exec -a -bash bash)
ちなみに exec
コマンドの -a
オプションは、bash、ksh、zsh などでは実装されていますが、FreeBSD shなどでは実装されておらず、POSIX でも標準化されていません。
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 -o
の login_shell
、zsh の場合は set -o
の login
で調べることができます。興味深い例ですが、一行目に「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
による設定方法を採用しています。
- シェルをログインモードで起動すると
.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
を読み込む
- (ksh93) 環境変数
環境変数 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
を読み込む
- (mksh) 環境変数
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 シェルを超える対話機能を実装しています。他のシェルよりも複雑な設定ファイルの読み込み方法を採用しているため、どうしても説明することが多くなってしまいます。
- シェルをログインモードで起動すると、
.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
のような呼び出し方に変更する方法も考えられます。以下はこの話に関するその他の関連記事です。
- https://superuser.com/questions/750747/why-does-my-bashrc-get-read-when-i-run-noninteractive-commands-over-ssh
- https://superuser.com/questions/965987/why-does-rsync-read-bashrc-and-not-profile
zsh
zsh は ksh と C シェルを参考にしながらも、互換性よりも独自路線を選び多くの拡張機能を備えたシェルです。bash と同じように ksh88 と ksh93 のサブセットを実装しています。zsh の設定方法は、ksh93 や bash とも違い固定のファイル名を読み込むシンプルな方法です。
- シェルをログインモードで起動すると
.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
に対応しています。.zprofile
は zsh 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 ファイルだけです。この記事で設定ファイルに対する理解が広まれば幸いです。