TL;DR;
コマンドを打った時、その実体となる実行可能ファイル(exeなど)がどこにあるのか探しに行く先が環境変数PATH
だから
初めに
ソフトウェア開発の入門書などを読んでいると、「PATHを通しましょう」と言われますよね。環境変数のあれです。いわゆるおまじないですが、なんでこんなことしなきゃいけないのかについて?
- 環境変数のPATHとファイルのパスがややこしいので、ここでは前者をPATH、後者をパスと書きます。
- Win/Linux/Mac 大体同じです。少なくとも考え方は同じです。一応パスなどはWindows流で書いてます。
そもそも環境変数とは
「変数=値のリスト」の形式でシステムに値を登録しておく手段です。
例えばMAIL=hoge@example.com
としておけば、cmdでMAIL
と打てば長たらしいアドレスを打たなくて済むわけです。グローバル変数みたいなもんです。(OS全体で共有されるもの、ユーザー内だけで共有されるもの、そのプロセスだけで共有されるもの等ありますが、今回は気にしません。OSまたはユーザーの設定だと思ってください)
他にも、プログラム中にハードコードできないパスワードや、マシンによって変わるIPアドレスなどを環境変数に逃がして、プログラム中から参照できるようにしたりします。
プログラムの起動の仕方
cmdをはじめ、プログラムを起動するには
現在のディレクトリがc:\hoge\
で、その中にhuga.exe
があるなら、
$ huga.exe
と打てばOK。
また、面倒ですが、
$ c:\hoge\huga.exe
カレントディレクトリがc:\piyo\
でさっきのhoge.exe
を呼びたいなら相対パスで
$ ..\hoge\huga.exe
または絶対パスで
$ c:\hoge\huga.exe
となります。階層が深いと大変です。
これは、OSがプログラム名(コマンド)からプログラムのファイル実体を探しに行く先が、基本的にカレントディレクトリだからです。
PATHの役割
カレントディレクトリにないものは、絶対パスや相対パスで打つことになります。しかし例えばssh
(リモート操作のプログラム)なんかは毎回毎回ssh.exe
のあるディレクトリに移動するか、c:\Program\Openssh\bin\ssh.exe
なんて長たらしく打ってられません。そこでOSに「もうc:\Program\Openssh\bin\
はカレントディレクトリじゃなくても探してよ」と伝えましょう。cmdを再起動すれば、ssh
とだけ打てばよくなります。
この時OSに伝えたいディレクトリがPATH変数の中身です。OSがPATHを見て、「ここと、ここと、ここにsshがあるかもしれないから探そう」としてくれます。
モチロン、自動で探して欲しいディレクトリなんて沢山あるので、PATH変数の中身はセミコロン区切りで複数のパスを入れられるようになっています。
因みに.exe
も省略できるようになってます。
いいや、俺は絶対パス打つからいいよ!
ダメです。
cmdで実行するときはそれでもいいかもしれませんが、他のプログラムの中でそれを呼び出したい場合があります。例えばgit
の中ではssh
を呼び出します。gitの開発者はあなたのPCの中のssh.exe
の場所なんて知る由もありませんから、PATHに自分で登録してね!ということになっているのです。(インストーラがうまいことやってくれたりもしますが…
活用
自分で便利ツールを作ったとしましょう。実行すると「maki」と表示されるyuka.exe
です。これは頻繁に使いますね。
ファイルの在りかはc:\hogehoge\piyopiyo\znd\znd\yee\yuka.exe
絶対パスでなんて打ちたくないですね!
環境変数PATHにc:\hogehoge\piyopiyo\znd\znd\yee
を登録、すると
$ yuka
maki
こんな感じです。公開されているプログラムだと大体インストーラが(Linuxならmakeとかyum、apt-get)がやってくれてますが。中では環境変数PATHに登録してくれているので、何も考えずにコマンドが使えるようになっていたのです。
javacもadbもpythonもみんな実体となるexeがどこかにあって、PATHにディレクトリが登録することで、普通のコマンドと同様に使えるようにしてるのです!
環境変数が格納されているメモリについて
スタック領域内の、main
関数の関数フレームよりも前(アドレス値としてはより大きい方)に格納されています。これは以下のようにすることで簡単に確認できます。
auto dummyData = '.';
char* ptr = &dummyData;
while(true)
{
cout << *ptr << ends; flush(cout);
ptr++;
}
プロセスの環境変数を書き換えた場合は?
先述の通り、環境変数の文字列本体はスタック領域に格納されていました。スタック領域は必要な分だけギチギチに詰まっているので、中身のデータ領域を拡張することはできません。
では、putenv
関数は一体何なのでしょうか?答えは単純で、引数に渡したポインタの差す領域が、その環境変数の領域としてそのまま使用されます。そのため、putenv
に渡した文字列は解放してはいけませんし、getenv
で取得したポインタは、あくまでその瞬間の値へのポインタであり、その後環境変数が変更された場合そのポインタは無効になっている可能性を考慮しなければならないというわけです。
char* hoge = "HOGE=FUGA";
putenv(hoge);
cout << (hoge + 5) == getenv("HOGE") << endl; // true