2013年04月16日

Windowsの環境変数の仕様についてメモ

以下、実際の挙動を確認したりMSDNのリファレンス読んだりプログラム書いてふむふむした結果。

確認には Windows XP SP3 を利用した。現在( Windows 8 )では、また少し仕様が変わっている可能性がある。過去( Windows 2000 以前、および Windows ME 以前)は仕様が異なっていた可能性もある。そういう意味で、正確性は保証しない。もし誤りがあったら、気軽に当記事のコメント欄やTBで指摘してほしい。

環境変数の種類

System、User、Process、Volatileの4種が在る。ただし、 Windows 95 系列にはこの内幾つかが存在しない模様。

それぞれ、以下の特徴を持つ。

System
OS全体で共有する環境変数。権限が無いと閲覧以外出来ない。
User
ユーザー(Windowsのアカウント)毎の環境変数。当然、別のユーザーの環境変数を読み書きする事は出来ない。
Process
そのプロセス内でのみ有効な環境変数。
Volatile
一時的な環境変数。作成してから、そのマシンがシャットダウンされるまで(ログアウトではなく)の間のみ有効。

内、Process環境変数は、System、User、Volatile全てを合わせた物となる。先に挙げた順に定義され、種類の異なる同名の環境変数は、より後に挙げた物で上書きされる。つまり、System環境変数VARを"system"、User環境変数VARを"%VAR%->user"、Volatile環境変数VARを"%VAR%->volatile"とした場合、最終的なProcess環境変数VARの値は"system->user->volatile"となる。

他の環境変数を参照する環境変数の定義

出来ない。まぁ当たり前か。

> SET V1=%V2%

> SET V2=hoge

> SET V1
V1=%V2%

Windowsログオン時のSystem、User環境変数

環境変数設定用ダイアログウィンドウが用意されている(後述)。

これらは辞書順に定義される。前項の仕様により、他の環境変数の値を設定する環境変数はうまく設定できない事がある。

例えば、以下のUser環境変数を設定しWindowsを再起動した後で各環境変数の値を(コマンドプロンプトでECHOコマンドを使うことで)確認すると、VAR01の値は「hoge」ではなく「%VAR02%」となっていることが確認できる。一度だけ再帰的な展開がなされるようである。

名前設定値
VAR01%VAR02%
VAR02%VAR03%
VAR03hoge
VAR04%VAR03%
VAR05%VAR04%
> ECHO %VAR01% %VAR02% %VAR03% %VAR04% %VAR05%
%VAR02% hoge hoge hoge hoge

参考

空白の扱い

環境変数の値に空白が含まれていた場合、その展開結果は一つの値と解釈されず、空白で区切られたトークンの列挙として見られる。

例えば以下の通り。

> TYPE listargs.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
	int i;
	printf("length:\t%d\n", argc - 1);
	for(i = 1; i < argc; ++i) {
		printf("arg\t%d:\t%s\n", i, argv[i]);
	}
	return 0;
}

> gcc -o listargs.exe listargc.c

> listargs.exe foo bar
length:	2
arg	1:	foo
arg	2:	bar

> SET VAR=hoge piyo

> listargs.exe %VAR%
length:	2
arg	1:	foo
arg	2:	bar

ダブルクォーテーションで囲めば1つの値として認識される。

> listargs.exe "%VAR%"
length:	1
arg	1:	foo bar

CDなど、「1つの引数しか取らない」コマンドであれば、クォートしなくてもよしなに扱ってくれる(事がある)。これは、コマンドプロンプトに限らず「ファイル名を指定して実行」でも同様である。

ちなみに、どうやらsh(bash?)スクリプトもこの方式のようだ。zshはクォートしなくても一つの値として認識する。(何かオプションがあるのかもしれない)

PATH

PATHは特殊な環境変数だ。Windowsは、カレントディレクトリ内に見つからなかったファイルを、この環境変数に設定されたパスから探し出そうとする。「;」で区切る事で、複数のパスを記述する事が出来、その際は先頭の物ほど優先される。

必ず、System環境変数のそれとUser環境変数のそれが結合された物が各Process環境変数として設定される

各環境変数の操作方法

WindowsのUI

Windows XP では、専用のダイアログウィンドウが用意されている。

スタート」メニューの「コントロール パネル」サブメニュー内の「システム」で「システムのプロパティ」ダイアログウィンドウを表示し、それの「詳細設定」タブ内「環境変数」ボタンで表示される「環境変数」ダイアログウィンドウで、System環境変数、User環境変数がそれぞれ設定、確認可能。

Windows 95 系列には、専用のUIは用意されていない。

記憶領域

NT系列のWindowsでは、System環境変数、User環境変数はレジストリに記録される。Process環境変数、Volatile環境変数についての記述は発見できなかったが、性質から言ってオンメモリだと思われる。

環境変数が記憶されるレジストリ上の場所
SystemHKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
UserHKEY_CURRENT_USER\Environment

95系列のWindowsでは、C:\AUTOEXEC.BATファイル内でSETコマンドを用いて設定する。

Win32 API

Process環境変数用の物のみAPIが用意されている。System、User環境変数は直接レジストリを参照/編集する必要がある。

また、文字列中の環境変数を展開する為のAPIも用意されている。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <windows.h>
#include <tchar.h>

#define BUF_SIZE 1024

void set_and_get(void) {
	_TCHAR buf[BUF_SIZE];

	// (環境変数FOOの値を"BAR"に)設定
	SetEnvironmentVariable(_T("FOO"), "BAR");

	// (環境変数FOOの値を)取得
	GetEnvironmentVariable(_T("FOO"), buf, BUF_SIZE);

	_putts(buf);
}

void block(void) {
	LPTSTR block;

	// 環境変数ブロックを取得
	block = GetEnvironmentStrings();

	while(*block != '\0') {
		_putts(block);
		block += _tcslen(block) + 1;
	}

	// 環境変数ブロックを解放
	FreeEnvironmentStrings(block);
}

void expand(void) {
	_TCHAR buf[BUF_SIZE];

	// 環境変数を含む文字列を展開する
	ExpandEnvironmentStrings(_T("FOO variable: %FOO%"), buf, BUF_SIZE);
	_putts(buf);
}

レジストリ編集によりSystem、User環境変数を変更した場合は、それをWindowsに通知する必要がある。

#include <windows.h>
#include <tchar.h>

void notify(void) {
	HKEY hkey;
	const _TCHAR value[] = _T("VALUE");
	DWORD dwReturnValue;

	// (レジストリに直接書き込む事で)User環境変数HOGEを作る
	RegOpenKeyEx(HKEY_CURRENT_USER, _T("Environment"), 0, KEY_SET_VALUE, &hkey);
	RegSetValueEx(hkey, _T("HOGE"), 0, REG_EXPAND_SZ, (BYTE *)value, (_tcslen(value) + 1) * sizeof(_TCHAR));

	// システムに環境変数の変更を通知する
	SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, &dwReturnValue);

	RegCloseKey(hkey);
}
参考

.NET Framework

.NET Framework 2.0より可能。System、User、Process各環境変数の取得/設定ができる。Volatile環境変数用の物は用意されていない。

// C#での例
class TestEnv {
	static void set_and_get() {
		// Process環境変数FOOの値を"BAR"に設定
		System.Environment.SetEnvironmentVariable("FOO", "BAR", System.EnvironmentVariableTarget.Process);
		// Processの場合は第3引数を省略できる
		// System.SetEnvironmentVariable("FOO", "BAR");

		// Process環境変数FOOの値を取得
		string buf = System.Environment.GetEnvironmentVariable("FOO", System.EnvironmentVariableTarget.Process);
		// Processの場合は第2引数を省略できる
		// string buf = System.GetEnvironmentVariable("FOO");

		System.Console.WriteLine(buf);
	}

	static void all() {
		// 全Process環境変数の名前とその値を取得
		System.Collections.IDictionary vars = System.Environment.GetEnvironmentVariables(System.EnvironmentVariableTarget.Process);
		// Processの場合は引数を省略できる
		// IDictionary vars = System.Environment.GetEnvironmentVariables();

		foreach(System.Collections.DictionaryEntry i in vars) {
			System.Console.WriteLine("{0}: {1}", i.Key, i.Value);
		}
	}

	static void expand() {
		// 環境変数を含む文字列を展開する
		string buf = System.Environment.ExpandEnvironmentVariables("FOO variable: %FOO%");

		System.Console.WriteLine(buf);
	}
}
参考

コマンドプロンプト

定義できるのはProcess環境変数のみ。

(Vista以降であればSETXというコマンドでUser及びSystem環境変数が設定できるらしいが未確認。)

> REM 環境変数FOOの値を"BAR"に設定

> SET FOO=BAR

> REM 環境変数FOOの値を表示

> SET FOO
FOO=BAR

> REM 環境変数の一覧を表示

> SET
ALLUSERSPROFILE=C:\Documents and Settings\All Users
APPDATA=C:\Documents and Settings\AmaiSaeta\Application Data
CLIENTNAME=Console
CommonProgramFiles=C:\Program Files\Common Files
COMPUTERNAME=MYCOMPUTER
ComSpec=C:\WINDOWS\system32\cmd.exe
(長いので省略)

> REM 環境変数を含む文字列を展開(して、出力)

> ECHO FOO variable: %FOO%
FOO Variable: BAR

> REM PATHについては以下の方法でも可

> REM 設定

> PATH C:\foo

> REM 表示

> PATH
PATH=C:\foo
参考

Windows Scripting Host

System、User、Volatile各環境変数の取得設定が可能。Process環境変数は取得のみ可能で設定は出来ない。設定しようとしても何のエラーも発生しないので注意が必要。

// JScriptでの例
function set_and_get() {
	var sh, env, buf;

	// User環境変数のコレクションを取得
	sh = WScript.CreateObject('WScript.Shell');
	envs = sh.Environment('User');

	// User環境変数FOOの値を"BAR"に設定
	envs('FOO') = 'BAR';

	// User環境変数FOOの値を取得
	buf = envs('FOO');

	WScript.Echo(buf);
}

function all() {
	var envs = WScript.CreateObject('WScript.Shell').Environment('User');
	var buf = [];

	// VBSであれば単純に For Each 出来るが、JScriptではEnumeratorオブジェクトを使う必要がある。
	for(var i = new Enumerator(envs); !i.atEnd(); i.moveNext()) {
		buf.push(i.item());
	}
	WScript.Echo(buf.join('\n'));
}

function expand() {
	var sh = WScript.CreateObject('WScript.Shell');
	var buf;

	buf = sh.ExpandEnvironmentStrings('FOO variable: %FOO%');

	WScript.Echo(buf);
}
参考

Windows PowerShell

PowerShellには、さながらファイルシステムのドライブ(Cドライブ、Dドライブ……)のように各種データ群へアクセス出来る"ドライブ"という機能があり環境変数へはEnvドライブからアクセスする。

また、変数名の前に"$env:"を前置することで、値を参照することも可能。

おそらく、Process環境変数のみアクセス可能。

> # 環境変数FOOの値を"BAR"に設定
> Set-Item -Path Env:FOO -Value "BAR"

> # 同上
> Set-Item Env:FOO "BAR"

> # 同上
> $env:FOO = "BAR"

> # 環境変数FOOの値を取得
> $var = (Get-Item env:FOO).Value

> # 同上
> $var = $env:FOO

> $v
BAR

> # 環境変数の一覧を取得
> $vars = (Get-ChildItem Env:)

> $vars

Name                           Value
----                           -----
ALLUSERSPROFILE                C:\Documents and Settings\All Users
APPDATA                        C:\Documents and Settings\AmaiSaeta\Application Data
CLIENTNAME                     Console
CommonProgramFiles             C:\Program Files\Common Files
COMPUTERNAME                   MYCOMPUTER
ComSpec                        C:\WINDOWS\system32\cmd.exe
(長いので省略)
参考
  • PowerShellの "Get-Help about_environment_variables" コマンド
System、User、Process、Volatile
MSDNWshShell.Environmentの引数より。正式には何というのかは不明。当記事ではこの表記で統一している。
Windowsに通知する必要がある。
という事になっている筈だが、どうも通知しなくても問題ないように見える。

今回の検証で、初めてC#に触れたが、一番最初の用途がこれってのは、それはそれでどうなんだろう……

posted by 天井冴太 at 00:47| Comment(0) | TrackBack(0) | OS | 更新情報をチェックする
この記事へのコメント
コメントを書く
コチラをクリックしてください

この記事へのトラックバック