できない.dev

環境変数の一覧を取得するには

プロセスに渡っている環境変数を名前と値の組としてすべて取り出す基本形を各言語で示す。
名前順に並べる書き方と接頭辞で絞り込む書き方、一覧をそのまま出力してはいけない理由までを扱う。

公開:

各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 API ドキュメントで確認したものである。

Python 実行確認済み

import os
 
# os.environ は名前→値のマッピング。items() で全件を回せる
for name, value in sorted(os.environ.items()):
    print(f"{name}={value}")   # HOME=/home/alice のように 1 行ずつ出る
 
# 名前だけの一覧や、接頭辞での絞り込みも辞書と同じ書き方でできる
print(sorted(n for n in os.environ if n.startswith("PYTHON")))

os.environ は辞書と同じインターフェースを持つマッピングなので、items() や内包表記がそのまま使える。
sorted で名前順にしておくと、環境ごとの差分を目で追いやすくなる。

JavaScript 実行確認済み

// process.env は名前→値のオブジェクト。値はすべて文字列
const entries = Object.entries(process.env).sort(([a], [b]) => a.localeCompare(b));
for (const [name, value] of entries) {
  console.log(`${name}=${value}`); // HOME=/home/alice のように 1 行ずつ出る
}
 
// 接頭辞で絞り込む
const npmVars = Object.keys(process.env).filter((n) => n.startsWith("npm_"));
console.log(npmVars);

process.env は素のオブジェクトなので Object.entries や Object.keys で回せる。
順序は登録順で安定しないため、比較や表示に使うときは名前で並べ替えてから出す。

うまくいかない時: Next.js で環境変数 (process.env) が読み込めない

TypeScript 実行確認済み

// process.env の型は NodeJS.ProcessEnv(値は string | undefined)
const entries: [string, string | undefined][] = Object.entries(process.env);
for (const [name, value] of entries.sort(([a], [b]) => a.localeCompare(b))) {
  console.log(`${name}=${value ?? ""}`);
}
 
// 値が undefined になり得るので、Record<string, string> に詰め直すときは除外する
const defined: Record<string, string> = Object.fromEntries(
  entries.filter((e): e is [string, string] => e[1] !== undefined)
);
console.log(Object.keys(defined).length);

NodeJS.ProcessEnv の値は string | undefined なので、Record<string, string> として扱いたいときは型ガードで undefined を落としてから詰め直す。
undefined のまま連結すると文字列の "undefined" が混ざる。

うまくいかない時: Vite で環境変数(import.meta.env)が読み込めない

Go 静的確認

package main
 
import (
	"fmt"
	"os"
	"sort"
	"strings"
)
 
func main() {
	// os.Environ は "NAME=value" 形式の文字列スライスを返す
	env := os.Environ()
	sort.Strings(env)
	for _, kv := range env {
		name, value, _ := strings.Cut(kv, "=") // 値に = が含まれても最初の 1 つで割る
		fmt.Printf("%s=%s\n", name, value)
	}
 
	// 接頭辞で絞り込む
	for _, kv := range env {
		if strings.HasPrefix(kv, "GO") {
			fmt.Println(kv)
		}
	}
}

os.Environ は名前と値を = で連結した文字列のスライスを返すため、値に = を含む変数(PATH の一部など)を壊さないよう strings.Cut で最初の = だけを区切りにする。
名前だけの一覧が欲しいときも同じ分割で取り出す。

Rust 静的確認

use std::env;
 
fn main() {
    // env::vars は (名前, 値) の String ペアを返すイテレータ
    let mut vars: Vec<(String, String)> = env::vars().collect();
    vars.sort();
    for (name, value) in &vars {
        println!("{name}={value}");
    }
 
    // 接頭辞で絞り込む
    let cargo_vars: Vec<&String> = vars
        .iter()
        .filter(|(name, _)| name.starts_with("CARGO"))
        .map(|(name, _)| name)
        .collect();
    println!("{cargo_vars:?}");
 
    // UTF-8 でない値が混ざる環境では env::vars が panic するので vars_os を使う
    println!("{}", env::vars_os().count());
}

env::vars は値が UTF-8 でないと panic するので、素性の分からない環境を走査するときは OsString を返す vars_os を使う。
ソートやフィルタは Vec に集めてから通常のイテレータ操作で行う。

Java 実行確認済み

import java.util.Map;
import java.util.TreeMap;
 
public class ListEnvVars {
    public static void main(String[] args) {
        // System.getenv() は変更不可の Map<String, String> を返す。TreeMap に移して名前順に並べる
        Map<String, String> env = new TreeMap<>(System.getenv());
        env.forEach((name, value) -> System.out.println(name + "=" + value));
 
        // 接頭辞で絞り込む
        env.keySet().stream()
            .filter(n -> n.startsWith("JAVA"))
            .forEach(System.out::println);
    }
}

System.getenv() は変更できない Map<String, String> を返す。
順序は不定なので TreeMap に移して名前順にし、絞り込みは keySet のストリームで行う。

C# 実行確認済み

using System.Collections;
 
// GetEnvironmentVariables は非ジェネリックの IDictionary を返すので DictionaryEntry に読み替える
var env = Environment.GetEnvironmentVariables()
    .Cast<DictionaryEntry>()
    .OrderBy(e => (string)e.Key, StringComparer.OrdinalIgnoreCase)
    .ToList();
 
foreach (var e in env)
{
    Console.WriteLine($"{e.Key}={e.Value}");
}
 
// 接頭辞で絞り込む
var dotnetVars = env
    .Where(e => ((string)e.Key).StartsWith("DOTNET", StringComparison.OrdinalIgnoreCase))
    .Select(e => (string)e.Key);
Console.WriteLine(string.Join(", ", dotnetVars));

Environment.GetEnvironmentVariables は非ジェネリックの IDictionary を返すので、Cast<DictionaryEntry> で LINQ に載せてから並べ替える。
Windows では名前の大文字小文字が区別されないため、比較子も OrdinalIgnoreCase に揃える。

つまずき

「設定したはずの変数が一覧に無い」ときは、その変数が本当に環境変数になっているかを疑う。
シェルで FOO=1 と代入しただけの変数はシェルの中だけのもので、export しなければ子プロセスには渡らない。
.env ファイルの内容も、dotenv 系のライブラリで読み込むまでは一覧に含まれない。
逆に、一覧に出ているのに値が古いときは、起動時にコピーされた環境を見ているだけで、後からターミナル側で変えた値は反映されていない。

一覧をそのままログに出さない

環境変数には API トークンやデータベースの接続文字列が入っていることが多く、一覧をそのまま標準出力や CI のログに流すと秘密情報が残る。
デバッグで存在を確かめたいときは名前だけを出すか、値の先頭数文字だけを残してマスクする。
ライブラリのデバッグ出力が環境全体をダンプすることもあるので、本番のログレベルにも注意する。

Windows と Unix で名前の扱いが違う

Unix 系では環境変数の名前は大文字と小文字を区別し、PATH と Path は別の変数になる。
Windows では区別されず、同じ変数が実行環境によって Path や PATH として見える。
両方で動かすコードでは、名前の比較や絞り込みを大文字小文字を無視する形にしておくと、片方の OS でだけ一覧から漏れる事故を防げる。

この記事は役立ちましたか?