できない.dev

環境変数を設定するには

プログラムの中から環境変数を設定する基本形を各言語で示す。
設定が及ぶ範囲がそのプロセスと子プロセスに限られる点と、子プロセスにだけ値を渡す書き方までを扱う。

公開:

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

Python 実行確認済み

import os
import subprocess
 
os.environ["API_TOKEN"] = "abc123"          # このプロセスと、以後起動する子プロセスに効く
print(os.environ["API_TOKEN"])
 
# 子プロセスにだけ渡したいときは環境を組み立てて env= に渡す
child_env = {**os.environ, "LOG_LEVEL": "debug"}
subprocess.run(
    ["python", "-c", "import os; print(os.environ['LOG_LEVEL'])"],
    env=child_env,
)

os.environ への代入は裏で putenv を呼ぶため、以後に起動する子プロセスへも値が引き継がれる。
値は文字列でなければならず、整数を代入すると TypeError になる。
env= に辞書を渡した場合は親の環境を引き継がないので、os.environ を展開して差分だけを足すのが安全である。

JavaScript 実行確認済み

process.env.API_TOKEN = "abc123";           // 値は文字列に変換される
console.log(process.env.API_TOKEN);
 
const { execFileSync } = require("node:child_process");
const out = execFileSync("node", ["-e", "console.log(process.env.LOG_LEVEL)"], {
  env: { ...process.env, LOG_LEVEL: "debug" }, // 子プロセスにだけ渡す
});
process.stdout.write(out.toString());

process.env への代入は値を文字列へ変換するので、数値を入れても取り出すときは文字列になる。
削除は delete process.env.API_TOKEN で行う。
child_process のオプション env を省略すると親の環境がそのまま渡るため、一部だけ変えたいときはスプレッドで展開して上書きする。

うまくいかない時: npm scripts で環境変数が設定できない(Windows で動かない)Node.js で dotenv の値が読み込めない

TypeScript 実行確認済み

process.env.API_TOKEN = "abc123";
 
// process.env のプロパティは string | undefined 型なので、読み出し側で絞り込む
const token: string = process.env.API_TOKEN ?? "";
console.log(token);
 
// 数値をそのまま代入すると型エラーになる。String() で明示的に文字列にする
process.env.PORT = String(3000);
console.log(typeof process.env.PORT, process.env.PORT);

代入の挙動は JavaScript と同じで、変わるのは型の扱いだけである。
@types/node は process.env の値を string | undefined と定義しているため、数値を渡すとコンパイルエラーになり、読み出し側では ?? などで既定値を与える必要がある。

うまくいかない時: Node.js で dotenv の値が読み込めない

Go 静的確認

package main
 
import (
	"fmt"
	"os"
	"os/exec"
)
 
func main() {
	os.Setenv("API_TOKEN", "abc123")
	fmt.Println(os.Getenv("API_TOKEN"))
 
	// 子プロセスにだけ渡すなら Cmd.Env を "KEY=VALUE" のスライスで組み立てる
	cmd := exec.Command("printenv", "LOG_LEVEL")
	cmd.Env = append(os.Environ(), "LOG_LEVEL=debug")
	out, err := cmd.Output()
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(string(out))
}

os.Setenv はこのプロセスの環境を書き換え、error を返すので本来は確認したほうがよい。
exec.Cmd の Env を nil のままにすると親の環境がそのまま引き継がれるため、一部だけ変えたいときは os.Environ() に追記する。
同じキーが重複した場合は後ろの値が優先される。

Rust 静的確認

use std::process::Command;
 
fn main() {
    // 子プロセスにだけ渡す。こちらは安全な API で完結する
    let out = Command::new("printenv")
        .arg("LOG_LEVEL")
        .env("LOG_LEVEL", "debug")
        .output()
        .expect("failed to run printenv");
    print!("{}", String::from_utf8_lossy(&out.stdout));
 
    // 自プロセスの環境を書き換える場合(edition 2024 では unsafe)
    unsafe { std::env::set_var("API_TOKEN", "abc123") };
    println!("{}", std::env::var("API_TOKEN").unwrap());
}

edition 2024 で std::env::set_var と remove_var は unsafe になった。
他スレッドが getenv を呼んでいる最中に環境を書き換えるとデータ競合になりうるためで、呼ぶ側がシングルスレッドであることを保証する責任を負う。
子プロセスへ渡すだけなら Command::env で足りるので、そちらを優先するとよい。

Java 実行確認済み

import java.util.Map;
 
public class Main {
    public static void main(String[] args) {
        // System.getenv() は読み取り専用。put すると UnsupportedOperationException になる
        try {
            System.getenv().put("API_TOKEN", "abc123");
        } catch (UnsupportedOperationException e) {
            System.out.println("System.getenv() は書き換えられない");
        }
 
        // 子プロセスへ渡す環境は ProcessBuilder のコピーに対して設定する
        ProcessBuilder pb = new ProcessBuilder("java", "-version");
        Map<String, String> env = pb.environment();
        env.put("LOG_LEVEL", "debug");
        System.out.println(pb.environment().get("LOG_LEVEL"));
    }
}

Java には自プロセスの環境変数を変更する標準 API が無く、System.getenv() が返すマップは変更不可である。
書き換えられるのは ProcessBuilder.environment() が返す「親の環境のコピー」だけで、これは start() で起動する子プロセスにのみ反映される。
自分の JVM に値を渡したいなら起動時に -D でシステムプロパティを使う。

C# 実行確認済み

using System;
using System.Diagnostics;
 
// 既定は Process スコープ。このプロセスと子プロセスにだけ効く
Environment.SetEnvironmentVariable("API_TOKEN", "abc123");
Console.WriteLine(Environment.GetEnvironmentVariable("API_TOKEN"));
 
// 子プロセスにだけ渡すなら ProcessStartInfo.Environment を使う
var psi = new ProcessStartInfo("dotnet", "--version");
psi.Environment["LOG_LEVEL"] = "debug";
Console.WriteLine(psi.Environment["LOG_LEVEL"]);

SetEnvironmentVariable は第 3 引数を省くと Process スコープになる。
Windows では EnvironmentVariableTarget.User や Machine を指定してレジストリへ永続化できるが、その場合も既に起動しているプロセスには伝わらない。
Unix 系では Process 以外のスコープが存在しないため、User や Machine を指定しても何も起きない。

うまくいかない時: PowerShell で設定した環境変数が別のウィンドウに反映されない

設定が届く範囲

プログラムの中で設定した環境変数が及ぶのは、そのプロセス自身と、設定より後に起動した子プロセスまでである。
呼び出し元のシェルには戻らないので、スクリプトの末尾で環境変数を設定しても、シェルに制御が戻った時点では消えている。
シェルの環境を変えたいなら、シェル側で export するか設定ファイルに書く必要がある。

つまずきやすい点

既に起動しているプロセスの環境は、あとから外部が書き換えることはできない。
設定したのに反映されないという場合、たいていは値の設定より先に対象のプロセスが起動している。
子プロセスへ渡す API に環境の辞書を丸ごと渡す形式では、親の環境を引き継がずに置き換えてしまう点にも注意する。
PATH まで消えてコマンドが見つからなくなるので、既存の環境を展開してから差分を足す。

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