Skip to content

Подстановка переменных

Библиотека env поддерживает использование ссылок на переменные в конфигурационных файлах, обеспечивая переиспользование конфигурации и динамическую замену значений.

Включение подстановки переменных

go
cfg := env.DefaultConfig()
cfg.ExpandVariables = true  // Включено по умолчанию

loader, _ := env.New(cfg)
loader.LoadFiles(".env")

Базовый синтаксис

Простые ссылки

bash
# Ссылка на другие переменные
BASE_URL=https://api.example.com
API_URL=${BASE_URL}/v1
# API_URL развёртывается в: https://api.example.com/v1

# Сокращённый синтаксис
HOST=localhost
URL=$HOST:8080
# URL развёртывается в: localhost:8080

Синтаксис значений по умолчанию

СинтаксисОписание
${VAR:-default}Если VAR не существует, использовать default
${VAR:=default}Если VAR не существует, использовать default (аналогично :-)
${VAR:?error}Если VAR не существует или пуста, вернуть ошибку

Ограничение самоссылки

Переменные, на которые ссылаются :-, :=, :?, должны отличаться от ключа, которому присваивается значение. Самоссылки вида KEY=${KEY:-default} распознаются как циклические ссылки и вызывают ошибку ErrExpansionDepth при загрузке. Для установки значения по умолчанию для ключа присваивайте литерал напрямую (KEY=default) или ссылайтесь на другие переменные (см. примеры ниже).


Подробный синтаксис

${VAR:-default} — значение по умолчанию

Наиболее распространённый синтаксис значения по умолчанию. Когда переменная не существует, используется значение по умолчанию; если переменная существует (даже пустая), используется исходное значение:

bash
# HOST определён, используется его значение
HOST=localhost
PRIMARY_HOST=${HOST:-127.0.0.1}
# PRIMARY_HOST развёртывается в: localhost

# TIMEOUT не определён, используется значение по умолчанию "30s"
TIMEOUT_VALUE=${TIMEOUT:-30s}
# TIMEOUT_VALUE развёртывается в: 30s

# Вложенные значения по умолчанию
DB_HOST=localhost
DB_URL=${DB_HOST}:${DB_PORT:-5432}
# При DB_HOST=localhost и DB_PORT не определён
# DB_URL развёртывается в: localhost:5432

Сценарии использования:

  • Значения по умолчанию для необязательных параметров конфигурации
  • Унификация конфигурации для сред разработки/продакшена

${VAR:=default} — значение по умолчанию

Поведение аналогично ${VAR:-default}: при отсутствии переменной используется значение по умолчанию:

bash
# DEBUG не определён, используется "false"
DEBUG_VALUE=${DEBUG:=false}

# CACHE_TTL не определён, используется значение по умолчанию
CACHE_TTL_VALUE=${CACHE_TTL:=3600}

Отношение с :-

${VAR:=default} в этой библиотеке полностью аналогично ${VAR:-default} по поведению. При отсутствии переменной в качестве результата подстановки используется значение по умолчанию. := не записывает значение по умолчанию обратно в хранилище переменных.


${VAR:?error} — сообщение об ошибке

Если переменная не существует или пуста, возвращается ошибка:

bash
# Если DATABASE_URL не определён, загрузка завершится ошибкой
DB_URL=${DATABASE_URL:?Database URL is required}

# Если API_TOKEN не определён, ошибка
AUTH_TOKEN=${API_TOKEN:?API_TOKEN must be set}

Сценарии использования:

  • Валидация обязательных параметров конфигурации
  • Ранний отказ для предотвращения ошибок во время выполнения

Экранирование

Экранирование знака доллара

Используйте $$ для литерала $:

bash
# Конфигурация цены
PRICE=$$99.99
# Развёртывается в: $99.99

# Строка, содержащая $
MESSAGE=Price is $$100
# Развёртывается в: Price is $100

Кавычки и подстановка

Подстановка переменных происходит на едином этапе постобработки после снятия кавычек, одинарные и двойные кавычки не влияют на подстановку переменных. Например SINGLE='${BASE}' (при BASE=hello) развёртывается в значение hello, аналогично поведению в двойных кавычках; если ссылочная переменная не определена (например LITERAL='${NO_EXPANSION}'), результатом будет пустая строка, а не сохранение литерала ${NO_EXPANSION}.

Различие между одинарными и двойными кавычками существует только при литеральном разборе: двойные кавычки обрабатывают escape-последовательности \n, \t и т. д., одинарные кавычки сохраняют как есть (без экранирования).

Внимание

Не используйте кавычки для «запрета подстановки». Для сохранения литерала ${VAR} используйте следующие способы:

bash
# Способ 1: экранирование знака доллара ($$ развёртывается в литерал $)
LITERAL='$${NO_EXPANSION}'
# Значение: ${NO_EXPANSION}
go
// Способ 2: отключение глобальной подстановки переменных
cfg := env.DefaultConfig()
cfg.ExpandVariables = false

Вложенная подстановка

Переменные могут ссылаться друг на друга вложенно:

bash
# Базовая конфигурация (избегайте встроенного запрещённого ключа ENV, используйте DEPLOY_ENV)
APP_NAME=myapp
DEPLOY_ENV=production

# Вложенные ссылки
DB_HOST=db.${DEPLOY_ENV}.example.com
# Развёртывается в: db.production.example.com

API_URL=https://${APP_NAME}.${DEPLOY_ENV}.api.example.com
# Развёртывается в: https://myapp.production.api.example.com

Обнаружение циклов

Библиотека автоматически обнаруживает циклические ссылки и возвращает ошибку:

bash
# Циклическая ссылка (ошибка)
A=${B}
B=${A}

# При загрузке возвращается ошибка ErrExpansionDepth

Ограничение глубины подстановки

Максимальная глубина подстановки по умолчанию — 5, жёсткий предел — 20:

go
cfg := env.DefaultConfig()
cfg.MaxExpansionDepth = 10  // Пользовательская глубина
КонстантаЗначениеОписание
DefaultMaxExpansionDepth5Значение по умолчанию (публичный API)

Подсказка

Жёсткий предел — 20 (внутреннее ограничение). Настроенный MaxExpansionDepth не может превышать этот предел.


Полный пример

bash
# .env файл

# Базовая конфигурация (избегайте встроенного запрещённого ключа ENV)
APP_NAME=myapp
DEPLOY_ENV=development
DEBUG=true

# Конфигурация базы данных
DB_HOST=localhost
DB_PORT=5432
DB_NAME=${APP_NAME}
DB_URL=postgres://${DB_HOST}:${DB_PORT}/${DB_NAME}

# Конфигурация API
API_BASE=https://api.${DEPLOY_ENV}.example.com
API_URL=${API_BASE}/v1

# Конфигурация логирования
LOG_LEVEL=info

# Цена (экранирование)
PRICE=$$99.99
go
package main

import (
    "fmt"
    "log"

    "github.com/cybergodev/env"
)

func main() {
    cfg := env.DefaultConfig()
    cfg.ExpandVariables = true

    loader, err := env.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
    defer loader.Close()

    err = loader.LoadFiles(".env")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("DB_URL:", loader.GetString("DB_URL"))
    fmt.Println("API_URL:", loader.GetString("API_URL"))
    fmt.Println("PRICE:", loader.GetString("PRICE"))
}

Связанная документация