(※タイトルの通り、本記事では Linux のみを対象として調査しています。Unix/非 Unix 問わず、それ以外の OS には当てはまらない部分が多いのでご注意ください)
答え
$HOME
環境変数を見ましょう。
Linux でのユーザ情報は「パスワードデータベース」(通常は /etc/passwd
ファイル。以下、本記事では単に "passwd" と略称)と呼ばれるところに格納されており、そこにユーザごとのホームディレクトリ情報も記載されています。
しかし passwd の情報を取得する getpwuid
(getpwnam
) の MAN ページに下記の通り記載があります:
アプリケーションが、ユーザーのホームディレクトリを決定する場合には、 (getpwuid(getuid())->pw_dir の値ではなく) HOME の値を検査するようにすべきである。 なぜなら、このようにすることで、ユーザーがログインセッション中で 「ホームディレクトリ」の意味を変更できるようになるからである。
最近、コンテナを実行する際に $HOME
を上書きしている例をちらほら見かけたのですが、
- 値を設定するユーザとしてアプリケーションがその値をちゃんと見てくれると期待していいものなのか?(環境変数無視して別の値を見に行ってしまうアプリケーションはないのか?)
- アプリケーション開発者として
$HOME
の値を信頼してもいいものなのか?
ともやもやしていました。
結論、どちらも YES。ユーザは $HOME
を上書きしていいし、アプリケーションはその値を正しい「ホームディレクトリ」として扱うべきということで、すっきりしました。
補遺 1: 誰が(最初に)$HOME
を設定するのか
答え: システム(OS)のログイン処理が passwd に記載の値を設定する。
これまた先ほど引用した man getpwnam
の一つ前の文に記載があります:
ログインプロセスは、このフィールドの値を使って、 ログインシェルの HOME 環境変数を初期化する。
また POSIX にも下記のような規定があります:
The system shall initialize this variable at the time of login to be a pathname of the user's home directory. See <pwd.h>.
(拙訳)
システムが、ログイン時にこの変数をユーザのホームディレクトリのパス名に設定すべきである。<pwd.h> 参照。
参考:
補遺 2: 世の実装はどうなっているのか?
ここからは完全に趣味の領域で長くなります。結びの言葉もありませんので、興味がない方はここでそっといただけると。
いくつかの言語のライブラリやアプリケーションで、ホームディレクトリの取得処理がどうなっているのかを実際に調査してみました。
基本的には「$HOME
環境変数を見る、なければ passwd を見る」という処理が多いようですが、前者だけで済ませている例もありました。
また、空文字の環境変数が設定されていたときの挙動についても、実装により差が見受けられます。差異にも注意が必要ですが、それ以上に空文字がそのまま使われるシステムでは ~/.config
が /.config
に展開されるということなので気をつける必要があります。
言語・ライブラリ | 環境変数 | 空文字の環境変数 | passwd | その他のフォールバック |
---|---|---|---|---|
Python os.path
|
✓ | 使われる | ✓ | |
Ruby Dir
|
✓ | 使われる | ✓ | |
Node.js os
|
✓ | 使われる | ✓ | |
Go os
|
✓ | 使われない | ||
Go os/user
|
✓ | |||
Go github.com/mitchellh/go-homedir
|
✓ | 使われない | ✓ | sh -c 'cd && pwd' |
Rust dirs /directories (dirs-sys ) |
✓ | 使われない | ✓ | |
Rust home /std::env
|
✓ | 使われる | ✓ | |
Rust home /std::env
|
✓ | 使われる | ✓ | |
Bash ~ の展開 |
✓ | 使われる | ✓ | |
Bash 引数なし cd
|
✓ | 使われる | ||
Ash ~ の展開・引数なし cd
|
✓ | 使われる | ||
Git | ✓ | 使われる |
Python: os.path.expanduser('~')
環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます。
Ruby: Dir.home
環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます。
Node.js: os.homedir()
環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます。
Go
os.UserHomeDir()
環境変数のみを見ます。空文字は未設定と同じ扱いになります。
os/user
os.UserHomeDir()
は Go 1.12 で追加されたものですが、それ以前に標準パッケージを使う場合は os/user
パッケージが利用されていたようです
ただしこれはあくまで passwd を扱うためのライブラリなので環境変数は特に見ませんし、CGO 必須でクロスコンパイルに向いていませんでした。
github.com/mitchellh/go-homedir
というわけで Go 1.12 以前では github.com/mitchellh/go-homedir
パッケージが人気でした(ちなみに @mitchellh さんは Hashicorp の創業者であり、Terraform や Vagrant などを作った方ですね!)。
これは環境変数と passwd の両方を見るだけでなく、それでもダメなら sh -c 'cd && pwd'
を実行するという処理が入っているのがユニークです(結局 cd
コマンドは何を見るの?となるので、果たしてこのフォールバックに意味があるのかはよくわかりません)。空文字は未設定と同じ扱いになります。
- homedir package - github.com/mitchellh/go-homedir - pkg.go.dev
- go-homedir/homedir.go at v1.1.0 · mitchellh/go-homedir
Rust
Rust は std::env::home_dir()
という関数があるのですが、Windows 上での動作に問題があるとのことで現在は deprecated になっています。ですので、いくつか thrid-party の crate も合わせて確認してみましょう。
dirs
/directories
(dirs-sys
)
dirs
と directories
が一般開発者に使われるライブラリで、実際の処理が内部用ライブラリ dirs-sys
にて実装されています。
環境変数と passwd の両方を見ます。空文字は未設定と同じ扱いになります。
- home_dir in dirs - Rust
- UserDirs in directories - Rust
- dirs-sys-rs/lib.rs at v0.3.4 · dirs-dev/dirs-sys-rs
home
/std::env
home
は Cargo などで使われている crate で、Unix 環境では単に公式の std::env::home_dir()
を使う実装になっています(前述の通り std::env::home_dir
が deprected なのは Windows 上での動作が問題になったのであり、Unix では問題なく動作する)。
環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます(home
crate のドキュメント上は「空文字は未設定扱い」となっていますが、間違っているようです。ref: brson/home#22)。
Bash
~
の展開
環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます。
引数なし cd
環境変数のみを見ます。空文字でも設定されていればその値が使われますが、それはつまり移動しないということです。
Busybox Ash
~
の展開、引数なし cd
のいずれでも環境変数しか見ません。空文字でも設定されていればその値が使われます。
Git
環境変数のみを見ます(git config --global
は $XDG_CONFIG_HOME
も見ます)。空文字でも設定されていればその値が使われます。
- Git - git-config Documentation
- Git - Environment Variables
-
-
git config --global
: git/path.c at v2.33.0 · git/git -
~
の展開: git/path.c at v2.33.0 · git/git
-