Skip to content

Leer variables de entorno con valores por defecto snippet

La configuración pertenece al entorno — pero getenv devuelve una cadena o nada, y todo consumidor aguas abajo quiere valores con tipo.

La configuración pertenece al entorno — pero getenv devuelve una cadena o nada, y todo consumidor aguas abajo quiere valores con tipo. Lee, parsea y aplica el DEFAULT en una única frontera al arranque, y luego pasa valores reales por todas partes; un programa que relee os.environ en lo profundo de un bucle no se puede testear, y uno que trata la cadena "false" como falsa invierte sus flags. Faltante y vacío son respuestas distintas, y los secretos leídos así nunca deben acabar en el log.

Receta ejecutable · 14 lenguajes
System & CLIenvconfigenvironment-variablescli12-factor

Every language

14 lenguajes, copy-ready. One at a time with syntax highlighting, or all inline.

JSJavaScript
// parse at the boundary, pass real values around after:
const port = Number(process.env.PORT ?? '8000');
const host = process.env.HOST ?? '127.0.0.1';

// dev only — loads .env into process.env before these reads:
// require('dotenv').config();

'' (empty string) is NOT missing — ?? keeps it, and Number('') is 0. If empty should fall back too, filter it explicitly. dotenv is a dev-time loader, never a production dependency.

TSTypeScript
function readInt(name: string, fallback: number): number {
  const raw = process.env[name];
  if (raw === undefined || raw === '') return fallback; // missing AND empty
  const n = Number.parseInt(raw, 10);
  return Number.isNaN(n) ? fallback : n;
}

const port = readInt('PORT', 8080);

Needs @types/node (or `declare const process`) for process.env to typecheck. parseInt never throws — it returns NaN, so the Number.isNaN check IS the error path.

GoGo
import (
	"fmt"
	"os"
	"strconv"
)

// read once at startup — everything downstream takes an int:
func mustPort() int {
	raw, ok := os.LookupEnv("PORT") // (value, set?) — the three-way answer
	if !ok {
		return 8080
	}
	n, err := strconv.Atoi(raw) // 'abc' and '' both land here
	if err != nil {
		panic(fmt.Sprintf("bad PORT %q", raw))
	}
	return n
}

os.Getenv flattens missing and empty into '' — LookupEnv's bool return splits them. Atoi is the typed boundary; never echo the value in errors when the var is a secret.

RsRust
use std::env;

fn read_port() -> u16 {
    env::var("PORT")
        .ok()
        .and_then(|v| v.parse::<u16>().ok())
        .unwrap_or(8080)
}

// strict: bubble both failures to the caller instead of a default
fn require_port() -> Result<u16, Box<dyn std::error::Error>> {
    Ok(env::var("PORT")?.parse::<u16>()?)
}

env::var fails NOT_PRESENT when unset and NotUnicode for non-UTF-8 values — the ? chain converts both. parse::<u16>() is the typed boundary; unwrap_or is the default.

PHPPHP
// trap: ?: fires on EVERY falsy string — '' and '0' hit the default too:
$host = getenv('HOST') ?: '127.0.0.1';

// getenv returns false (not null) when missing — test exactly that:
$raw = getenv('PORT');
$port = ($raw === false || $raw === '') ? 8000 : (int) $raw;

// ?? is the null-safe form for the superglobals, not for getenv:
$host = $_SERVER['HOST'] ?? '127.0.0.1';

?: conflates '0' and '' with missing — the falsy-string trap. ?? never fires for getenv's false return; it is the safe form on $_ENV/$_SERVER, where a missing key is null.

PyPython
import os

port = int(os.environ.get('PORT', '8080'))  # typed at the boundary
host = os.environ['HOST']                   # strict: KeyError names the var
debug = os.environ.get('DEBUG') == '1'      # explicit compare, never bool()

os.getenv('K', d) is the same get-with-default over os.environ. The bool trap: bool('false') is True — every non-empty string is truthy — so compare against the exact allowed spellings.

CC
#include <stdlib.h>

// getenv returns char* or NULL — parse at the boundary, pass longs around:
long read_port(void) {
    const char *raw = getenv("PORT");
    if (raw == NULL || *raw == '\0') return 8080; // missing AND empty
    char *end;
    long n = strtol(raw, &end, 10);
    return *end == '\0' ? n : 8080; // trailing junk = parse failure
}

The string is owned by the environment — never free() it, and a later setenv can invalidate the pointer. Missing vs empty are NULL vs "" — two different answers. strtol signals by end-pointer, not by return: *end == '\0' is the whole check.

C++C++
#include <cstdlib>
#include <stdexcept>
#include <string>

int read_port() {
    const char *raw = std::getenv("PORT");
    if (raw == nullptr || *raw == '\0') return 8080;
    try {
        return std::stoi(raw); // the two throws live here
    } catch (const std::invalid_argument &) { // "abc", ""
        return 8080;
    } catch (const std::out_of_range &) {      // huge values
        return 8080;
    }
}

std::stoi throws std::invalid_argument on garbage and std::out_of_range on overflow — and stoi("") throws rather than returning 0, unlike C's strtol. std::getenv has the same environ ownership as C: the pointer is not yours to free, and it is not std::string.

C#C#
int port = int.TryParse(
    Environment.GetEnvironmentVariable("PORT"),
    out var parsed) ? parsed : 8080;

string? host = Environment.GetEnvironmentVariable("HOST"); // null when missing

GetEnvironmentVariable returns null when unset — TryParse(null) is false, so the default falls out with no null branch. Real apps read IConfiguration (appsettings.json + env overlay), not raw env access.

JvJava
int port = Integer.parseInt(
        System.getenv().getOrDefault("PORT", "8080"));

// required — a named message beats NumberFormatException on null:
String host = System.getenv("HOST");
if (host == null) {
    throw new IllegalStateException("HOST is not set");
}

System.getenv is a snapshot taken at JVM start — setenv calls afterwards are invisible to it, so read once in main. Keep env values out of exception messages and logs when they are secrets.

SwSwift
import Foundation

let env = ProcessInfo.processInfo.environment // [String: String]
let port = Int(env["PORT"] ?? "") ?? 8000
let host = env["HOST"] ?? "127.0.0.1"

environment is a plain [String: String] — missing keys are nil, values stay strings. The double-?? chains default + parse; same API on macOS and Linux command-line tools.

KtKotlin
fun requireEnv(name: String): String =
    System.getenv(name) ?: error("missing env var: $name")

fun readEnv(name: String, default: String): String =
    System.getenv(name) ?: default

val port = readEnv("PORT", "8080").toInt()
val host = requireEnv("HOST")

Same JVM snapshot rule as Java. The elvis operator splits optional from required in one line each; error() names the missing var at startup instead of an NPE three frames later.

RbRuby
port  = ENV['PORT'] || '8080'   # nil (missing) falls back; '' does not
host  = ENV.fetch('HOST')       # required — KeyError when absent
debug = ENV['DEBUG'] == '1'     # values are strings; there is no to_bool

ENV is Hash-like with STRING keys AND string values only — parse once (Integer(port, 10)) and pass the number around. ENV.fetch('K', default) is the explicit-default spelling of ||.

ZigZig
const std = @import("std");

fn readPort(gpa: std.mem.Allocator) u16 {
    const raw = std.process.getEnvVarOwned(gpa, "PORT")
        catch return 8000; // error.EnvironmentVariableNotFound when unset
    defer gpa.free(raw);   // the caller owns the returned string
    return std.fmt.parseInt(u16, raw, 10) catch 8000;
}

getEnvVarOwned takes an allocator and errors when unset — you OWN the returned buffer, so defer free it. Read once into a config struct at startup; threading the allocator deep into call sites is the re-read anti-pattern.