環境変数を設定するには
プログラムの中から環境変数を設定する基本形を各言語で示す。
設定が及ぶ範囲がそのプロセスと子プロセスに限られる点と、子プロセスにだけ値を渡す書き方までを扱う。
公開:
各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 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 まで消えてコマンドが見つからなくなるので、既存の環境を展開してから差分を足す。