C言語のgetenvは、プロセスに設定された環境変数を文字列で取得できる標準関数です。
設定ファイルを用意せずに挙動を切り替えたり、PATHや一時ディレクトリなど実行環境のパスを知るのに便利です。
本記事では基本の呼び出し方、NULLの扱い、値の安全な使い方、OSごとの違いを、初心者の方にもわかりやすく解説します。
なお、扱うテーマは「環境変数を取得する(getenv)」に限定します。
C言語のgetenvとは?
環境変数を文字列で取得
getenvは、引数で指定した名前の環境変数を探し、その値をヌル終端された文字列のポインタとして返します。
例えばPATHやHOME(Linux/macOS)、USERPROFILE(Windows)などが代表的です。
存在しない環境変数名を指定した場合はNULLが返ります。
仕様の要点
- 戻り値はライブラリ内部の領域を指すポインタです。値を書き換えたり直接
freeしたりしてはいけません。 - 同一プロセス内で環境が更新されると(例: POSIXの
setenvやputenv)、指すアドレスや内容が変わる可能性があります。
ヘッダー
getenvは<stdlib.h>に宣言されています。
標準出力に表示する実例では<stdio.h>も使います。
#include <stdlib.h> // getenv
#include <stdio.h> // printf など
戻り値とNULL
char *が返り値です。
環境変数が見つからなければNULLになります。
C言語では文字列の長さは事前に分からないため、値をコピーする場合はstrlenで長さを測ってから領域を確保します。
NULLチェックを忘れると実行時エラーの原因になるので、必ず確認しましょう。
getenvの基本の使い方
最小サンプル
最小限の例として、OSに応じてホームディレクトリの環境変数(WindowsはUSERPROFILE、Linux/macOSはHOME)を取得して表示します。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// OSごとに代表的なホーム環境変数名を切り替えます
// Windows: USERPROFILE, Linux/macOS: HOME
#ifdef _WIN32
const char *name = "USERPROFILE";
#else
const char *name = "HOME";
#endif
// getenvで値を取得
const char *value = getenv(name);
// NULLかどうかを必ず確認
if (value != NULL) {
printf("%s=%s\n", name, value);
} else {
printf("%s is not set.\n", name);
}
return 0;
}
HOME=/home/alice
NULLチェック
存在しない環境変数はNULLが返るため、必ずチェックします。
NULLのままprintf("%s")などに渡すと未定義動作になります。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *name = "NOT_EXIST_ENV";
const char *value = getenv(name);
if (value == NULL) {
// 見つからなかった場合の扱いを決めておく
// 例: デフォルト値を使う、エラー終了する、など
printf("%s is not set (NULL returned).\n", name);
} else {
printf("%s=%s\n", name, value);
}
return 0;
}
NOT_EXIST_ENV is not set (NULL returned).
取得した文字列は変更しない
戻り値のポインタが指す領域はライブラリ側の所有物です。
値を直接変更すると未定義動作になるため、必要があれば自分のバッファにコピーしてから編集します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
const char *src = getenv("PATH");
if (src == NULL) {
puts("PATH is not set.");
return 0;
}
// 直接書き換えはNG: src[0] = 'X'; // 未定義動作
// 安全な方法: 自分のバッファにコピーしてから操作する
size_t len = strlen(src);
char *copy = (char *)malloc(len + 1);
if (!copy) {
fputs("malloc failed.\n", stderr);
return 1;
}
strcpy(copy, src); // コピー
// 例として、コピーの先頭にタグを付ける
// (実践ではsnprintfで安全に連結する方が推奨です)
printf("Original PATH (truncated view): %.60s...\n", src);
printf("Copied PATH (truncated view): %.60s...\n", copy);
// コピーを編集してもOK
if (len > 0) copy[0] = '#';
printf("Edited copy (truncated view): %.60s...\n", copy);
free(copy);
return 0;
}
Original PATH (truncated view): /usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin...
Copied PATH (truncated view): /usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin...
Edited copy (truncated view): #sr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin...
大文字/小文字の注意
POSIX系(Linux/macOS)では環境変数名は大文字小文字を区別します。
一方Windowsでは大文字小文字を区別しません。
そのため、移植性を意識して慣例的な大文字名を使うのが安全です。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *upper = getenv("PATH");
const char *mixed = getenv("Path");
#ifdef _WIN32
printf("On Windows, PATH: %s\n", upper ? upper : "(NULL)");
printf("On Windows, Path: %s\n", mixed ? mixed : "(NULL)");
#else
printf("On POSIX, PATH: %s\n", upper ? upper : "(NULL)");
printf("On POSIX, Path: %s\n", mixed ? mixed : "(NULL)"); // ふつうはNULL
#endif
return 0;
}
実行結果(例: Linux/macOS)
On POSIX, PATH: /usr/local/bin:/usr/bin:/bin
On POSIX, Path: (NULL)
実行結果(例: Windows)
On Windows, PATH: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem
On Windows, Path: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem
環境変数の取得例
PATHを読む
PATHはコマンド探索用ディレクトリの一覧です。
Linux/macOSは:区切り、Windowsは;区切りです。
分割する場合はstrtokなどを使いますが、分割の前に必ずコピーを作ることが重要です(戻り値を直接書き換えないため)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
const char *path = getenv("PATH");
if (!path) {
puts("PATH is not set.");
return 0;
}
// デリミタをOSごとに選択
#ifdef _WIN32
const char delim = ';';
#else
const char delim = ':';
#endif
char delim_str[2] = { delim, '#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
const char *path = getenv("PATH");
if (!path) {
puts("PATH is not set.");
return 0;
}
// デリミタをOSごとに選択
#ifdef _WIN32
const char delim = ';';
#else
const char delim = ':';
#endif
char delim_str[2] = { delim, '\0' };
// PATHは書き換え不可なので、コピーしてからstrtokする
char *buf = (char *)malloc(strlen(path) + 1);
if (!buf) {
fputs("malloc failed.\n", stderr);
return 1;
}
strcpy(buf, path);
printf("PATH entries:\n");
int i = 0;
for (char *tok = strtok(buf, delim_str); tok != NULL; tok = strtok(NULL, delim_str)) {
printf(" [%d] %s\n", i++, tok);
}
free(buf);
return 0;
}
' };
// PATHは書き換え不可なので、コピーしてからstrtokする
char *buf = (char *)malloc(strlen(path) + 1);
if (!buf) {
fputs("malloc failed.\n", stderr);
return 1;
}
strcpy(buf, path);
printf("PATH entries:\n");
int i = 0;
for (char *tok = strtok(buf, delim_str); tok != NULL; tok = strtok(NULL, delim_str)) {
printf(" [%d] %s\n", i++, tok);
}
free(buf);
return 0;
}
実行結果(例: Linux/macOS)
PATH entries:
[0] /usr/local/bin
[1] /usr/bin
[2] /bin
[3] /usr/local/sbin
[4] /usr/sbin
HOME/USERPROFILEを読む
ユーザーのホームディレクトリは、Linux/macOSはHOME、WindowsはUSERPROFILEが一般的です。
WindowsではHOMEDRIVEとHOMEPATHの組み合わせが使える場合もあります。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
#ifdef _WIN32
const char *home = getenv("USERPROFILE");
if (home) {
printf("Home: %s\n", home);
return 0;
}
// フォールバック: HOMEDRIVE + HOMEPATH
const char *drive = getenv("HOMEDRIVE");
const char *path = getenv("HOMEPATH");
if (drive && path) {
size_t len = strlen(drive) + strlen(path) + 1;
char *combined = (char *)malloc(len);
if (!combined) {
fputs("malloc failed.\n", stderr);
return 1;
}
// 例: "C:" + "\Users\alice" -> "C:\Users\alice"
strcpy(combined, drive);
strcat(combined, path);
printf("Home (fallback): %s\n", combined);
free(combined);
} else {
puts("Home directory is not set.");
}
#else
const char *home = getenv("HOME");
if (home) {
printf("Home: %s\n", home);
} else {
puts("HOME is not set.");
}
#endif
return 0;
}
Home: /home/alice
TEMP/TMPを読む
一時ファイルの保存先は、POSIXはTMPDIR(なければ/tmp)、WindowsはTMPまたはTEMPが一般的です。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
#ifdef _WIN32
const char *tmp = getenv("TMP");
if (!tmp) tmp = getenv("TEMP");
if (!tmp) tmp = "C:\\Windows\\Temp"; // 最終フォールバック(存在しない可能性もある点に注意)
#else
const char *tmp = getenv("TMPDIR");
if (!tmp) tmp = "/tmp"; // 代表的なデフォルト
#endif
printf("Temp directory: %s\n", tmp);
return 0;
}
Temp directory: /tmp
WindowsとLinuxの違い
名前の大小文字の扱いと、PATH区切り、代表的変数名に違いがあります。
移植性を高めるには、OSごとの慣例名を使い、区切り文字を条件コンパイルで切り替えます。
| 項目 | Linux/macOS(POSIX) | Windows | 備考 |
|---|---|---|---|
| 変数名の大文字小文字 | 区別する | 区別しない | 慣例的に大文字を使う |
| PATHの区切り | : | ; | 例: “/usr/bin:/bin” と “C:\Windows;C:\Windows\System32” |
| ホーム変数 | HOME | USERPROFILE(推奨)、HOMEDRIVE+HOMEPATH | GUI起動アプリでは例外的ケースに注意 |
| 一時ディレクトリ | TMPDIR(なければ/tmp) | TMP または TEMP | API側に専用関数がある場合も |
| パス区切り文字 | / | \ | C文字列ではバックスラッシュのエスケープに注意 |
Windows固有の補足: Microsoft Cランタイムには_dupenv_sやgetenv_sといった拡張もあります。
Unicodeを扱う場合は_wgetenvやWinAPIのGetEnvironmentVariableWも検討します。
動作確認の準備
macOS/Linux(Bash)で設定
Bashで一時的に環境変数を設定するにはexportを使います。
シェルを閉じると消えます。
# 一時設定
export SAMPLE_VALUE="hello"
echo "$SAMPLE_VALUE"
# 解除
unset SAMPLE_VALUE
永続化したい場合はシェルの初期化ファイル(例: ~/.bashrc, ~/.zshrc)に追記します。
echo 'export SAMPLE_VALUE="hello"' >> ~/.bashrc
# 反映
source ~/.bashrc
Windows(PowerShell)で設定
PowerShellでは$env:変数名でそのセッション限定の設定ができます。
# 一時設定
$env:SAMPLE_VALUE = "hello"
Write-Output $env:SAMPLE_VALUE
# 解除
Remove-Item Env:SAMPLE_VALUE
永続化はsetxを使います。
現在のセッションには即時反映されない点に注意してください。
# 永続設定(ユーザー環境)
setx SAMPLE_VALUE "hello"
Windows(CMD)で設定
CMDではsetが一時設定、setxが永続設定です。
:: 一時設定
set SAMPLE_VALUE=hello
echo %SAMPLE_VALUE%
:: 解除
set SAMPLE_VALUE=
:: 永続設定(次回のセッションから有効)
setx SAMPLE_VALUE hello
一時設定と永続設定
一時設定は「現在のプロセス(シェル)とそこから起動された子プロセスにだけ影響」し、シェルを閉じると消えます。
永続設定は「システムやユーザーのプロファイルに保存」され、新しいシェルや再ログイン後に有効になります。
アプリの動作確認では、まず一時設定で試し、必要に応じて永続化すると安全です。
また機密情報を環境変数に保存するのは避けるか、最小限に留めてください。
まとめ
getenvは環境変数を簡単に取得できる標準関数で、設定ファイルなしに挙動を切り替えたいときに役立ちます。
ポイントは次の通りです。
まず戻り値がNULLかを必ず確認し、取得した文字列は変更しないこと。
編集したい場合は自前のバッファにコピーします。
さらにOSごとの違い(PATHの区切り、HOME/USERPROFILE、TEMP/TMP、大小文字の扱い)を理解し、条件コンパイルやフォールバックで移植性を高めましょう。
必要に応じてWindows固有の_dupenv_sやUnicode対応APIも検討してください。
これらを押さえれば、PATHの分解、ホームディレクトリや一時ディレクトリの決定など、実用的な処理を堅牢に実装できます。
