direnv

Setup & Hook

eval "$(direnv hook bash)"         # bash (~/.bashrc)
eval "$(direnv hook zsh)"          # zsh (~/.zshrc)
direnv hook fish | source          # fish (~/.config/fish/config.fish)
eval "$(direnv hook elvish)"       # elvish

# Verify hook is active
echo $DIRENV_DIR
direnv status

.envrc Basics

# Create .envrc in project root
cat > .envrc << 'EOF'
export PROJECT_NAME="myapp"
export NODE_ENV="development"
export DATABASE_URL="postgres://localhost/myapp_dev"
PATH_add bin
EOF

direnv allow                       # approve the .envrc
direnv deny                        # deny the .envrc
direnv reload                      # reload after manual edit

Export Variables

export API_KEY="sk-test-123"
export DEBUG="true"
export LOG_LEVEL="debug"
export APP_PORT=8080

# Expand existing variables
export PATH="$HOME/.local/bin:$PATH"
export PYTHONPATH="$PWD/src:$PYTHONPATH"

# Use subshell results
export VERSION=$(git describe --tags --always)
export BRANCH=$(git rev-parse --abbrev-ref HEAD)

PATH Manipulation

PATH_add node_modules/.bin         # prepend to PATH
PATH_add ./bin                     # prepend ./bin
PATH_add "$HOME/go/bin"            # prepend Go bin
PATH_add .venv/bin                 # prepend Python venv

# MANPATH manipulation
MANPATH_add /usr/local/share/man

# Remove path entries on unload
PATH_rm node_modules/.bin

Layout Functions

# Python virtual environment
layout python

# Python with specific version
layout python3.11

# Node.js layout
layout node

# Go layout (sets GOPATH)
layout go

# Ruby layout
layout ruby

# Custom layout in function
layout_poetry() {
  source $(poetry env info --path)/bin/activate
}
layout_poetry

source_env / source_up

# Source another .envrc (relative path)
source_env ../shared/.envrc

# Source from environment variable
source_env "$HOME/.config/direnv/common.envrc"

# Walk up directory tree to find .envrc
source_up

# Source a .env file
source_env .env

# Conditional sourcing
if [ -f .env.local ]; then
  source_env .env.local
fi

use (node/python/go)

# Node.js version manager
use node                          # use .nvmrc / .node-version
use node 18                       # use specific node version

# Python version manager
use python 3.11                   # use specific python version
use python                        # use .python-version

# Go version
use go 1.21                       # use specific go version

# Ruby version
use ruby 3.2                      # use specific ruby version

# Multiple tools
use node 18
use python 3.11

watch_file

# Watch specific files for changes
watch_file .env
watch_file .nvmrc
watch_file .python-version
watch_file go.mod
watch_file pyproject.toml
watch_file package.json

# Watch with glob
watch_file*.json

# Reload when config changes
watch_file docker-compose.yaml
export COMPOSE_FILE="docker-compose.yaml"

dotenv Support

# Load .env file automatically
dotenv                            # loads .env
dotenv .env.local                 # load specific file
dotenv .env.development           # load development env

# Multiple dotenv files
dotenv .env .env.local

# Conditional dotenv
if [ -f .env ]; then
  dotenv
fi

# dotenv with fallback
dotenv_if_exists .env.local

Custom stdlib Functions

# Define custom functions in .envrc
has_command() {
  command -v "$1" &>/dev/null
}

set_java_home() {
  if has_command java; then
    export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java))))
  fi
}

set_java_home

# Lambda-style
export_sha256() {
  export "$1"=$(sha256sum "$2" | cut -d' ' -f1)
}

Allow / Deny

direnv allow                       # approve current .envrc
direnv allow .envrc                # approve specific file
direnv deny                        # deny current .envrc
direnv deny .envrc                 # deny specific file

# List allowed/denied
direnv status

# Edit and re-allow
$EDITOR .envrc && direnv allow

# Allow from outside directory
direnv allow /path/to/project/.envrc

Unload & Cleanup

# Automatic unload when leaving directory
# direnv unloads vars and restores previous env

# Manual unload
direnv export bash | reverse

# Cleanup function in .envrc
cleanup() {
  rm -f /tmp/$PROJECT_NAME-*
}
# Note: cleanup runs in subshell, use trap for real cleanup

# Check if in direnv context
if [ -n "$DIRENV_DIR" ]; then
  echo "inside direnv"
fi

Nested .envrc

# Parent: ~/projects/.envrc
export COMPANY="acme"
export DEFAULT_REGION="us-east-1"

# Child: ~/projects/myapp/.envrc
source_up                         # inherit parent settings
export PROJECT="myapp"
PATH_add ./node_modules/.bin

# Override parent values
export REGION="eu-west-1"         # overrides DEFAULT_REGION

# Directory structure
# ~/projects/
#   .envrc          (shared config)
#   myapp/
#     .envrc        (project config, sources parent)
#     api/
#       .envrc      (api-specific config)

Common Patterns

# Full-stack project .envrc
cat > .envrc << 'EOF'
export APP_ENV="development"
dotenv .env.development

layout python
PATH_add node_modules/.bin
PATH_add ./scripts

export DATABASE_URL="postgres://localhost:5432/myapp"
export REDIS_URL="redis://localhost:6379"
export API_PORT=3000

watch_file pyproject.toml
watch_file package.json
EOF

# Per-environment configuration
source_env .envrc.$(hostname)
if [ -f ".envrc.local" ]; then
  source_env .envrc.local
fi

# Docker Compose project
export COMPOSE_PROJECT_NAME="myapp"
export COMPOSE_FILE="docker-compose.yaml:docker-compose.dev.yaml"

# Secrets from vault
export DB_PASSWORD=$(cat /run/secrets/db_password 2>/dev/null || echo "dev")

安装与钩子

eval "$(direnv hook bash)"         # bash (~/.bashrc)
eval "$(direnv hook zsh)"          # zsh (~/.zshrc)
direnv hook fish | source          # fish (~/.config/fish/config.fish)
eval "$(direnv hook elvish)"       # elvish

# 验证钩子已激活
echo $DIRENV_DIR
direnv status

.envrc 基础

# 在项目根目录创建 .envrc
cat > .envrc << 'EOF'
export PROJECT_NAME="myapp"
export NODE_ENV="development"
export DATABASE_URL="postgres://localhost/myapp_dev"
PATH_add bin
EOF

direnv allow                       # 批准 .envrc
direnv deny                        # 拒绝 .envrc
direnv reload                      # 手动编辑后重新加载

导出变量

export API_KEY="sk-test-123"
export DEBUG="true"
export LOG_LEVEL="debug"
export APP_PORT=8080

# 展开已有变量
export PATH="$HOME/.local/bin:$PATH"
export PYTHONPATH="$PWD/src:$PYTHONPATH"

# 使用子命令结果
export VERSION=$(git describe --tags --always)
export BRANCH=$(git rev-parse --abbrev-ref HEAD)

PATH 操作

PATH_add node_modules/.bin         # 前置到 PATH
PATH_add ./bin                     # 前置 ./bin
PATH_add "$HOME/go/bin"            # 前置 Go bin
PATH_add .venv/bin                 # 前置 Python venv

# MANPATH 操作
MANPATH_add /usr/local/share/man

# 卸载时移除路径
PATH_rm node_modules/.bin

Layout 函数

# Python 虚拟环境
layout python

# 指定 Python 版本
layout python3.11

# Node.js 布局
layout node

# Go 布局 (设置 GOPATH)
layout go

# Ruby 布局
layout ruby

# 自定义布局函数
layout_poetry() {
  source $(poetry env info --path)/bin/activate
}
layout_poetry

source_env / source_up

# 引入另一个 .envrc (相对路径)
source_env ../shared/.envrc

# 从环境变量引用
source_env "$HOME/.config/direnv/common.envrc"

# 向上遍历目录查找 .envrc
source_up

# 引入 .env 文件
source_env .env

# 条件引入
if [ -f .env.local ]; then
  source_env .env.local
fi

use (node/python/go)

# Node.js 版本管理
use node                          # 使用 .nvmrc / .node-version
use node 18                       # 使用指定 node 版本

# Python 版本管理
use python 3.11                   # 使用指定 python 版本
use python                        # 使用 .python-version

# Go 版本
use go 1.21                       # 使用指定 go 版本

# Ruby 版本
use ruby 3.2                      # 使用指定 ruby 版本

# 多工具组合
use node 18
use python 3.11

watch_file

# 监视特定文件变化
watch_file .env
watch_file .nvmrc
watch_file .python-version
watch_file go.mod
watch_file pyproject.toml
watch_file package.json

# 使用 glob 监视
watch_file*.json

# 配置变化时重新加载
watch_file docker-compose.yaml
export COMPOSE_FILE="docker-compose.yaml"

dotenv 支持

# 自动加载 .env 文件
dotenv                            # 加载 .env
dotenv .env.local                 # 加载指定文件
dotenv .env.development           # 加载开发环境变量

# 多个 dotenv 文件
dotenv .env .env.local

# 条件 dotenv
if [ -f .env ]; then
  dotenv
fi

# 带回退的 dotenv
dotenv_if_exists .env.local

自定义标准库函数

# 在 .envrc 中定义自定义函数
has_command() {
  command -v "$1" &>/dev/null
}

set_java_home() {
  if has_command java; then
    export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java))))
  fi
}

set_java_home

# Lambda 风格
export_sha256() {
  export "$1"=$(sha256sum "$2" | cut -d' ' -f1)
}

允许 / 拒绝

direnv allow                       # 批准当前 .envrc
direnv allow .envrc                # 批准指定文件
direnv deny                        # 拒绝当前 .envrc
direnv deny .envrc                 # 拒绝指定文件

# 列出已允许/拒绝的
direnv status

# 编辑并重新批准
$EDITOR .envrc && direnv allow

# 从目录外批准
direnv allow /path/to/project/.envrc

卸载与清理

# 离开目录时自动卸载
# direnv 卸载变量并恢复之前的环境

# 手动卸载
direnv export bash | reverse

# .envrc 中的清理函数
cleanup() {
  rm -f /tmp/$PROJECT_NAME-*
}

# 检查是否在 direnv 上下文中
if [ -n "$DIRENV_DIR" ]; then
  echo "在 direnv 环境中"
fi

嵌套 .envrc

# 父级: ~/projects/.envrc
export COMPANY="acme"
export DEFAULT_REGION="us-east-1"

# 子级: ~/projects/myapp/.envrc
source_up                         # 继承父级设置
export PROJECT="myapp"
PATH_add ./node_modules/.bin

# 覆盖父级值
export REGION="eu-west-1"         # 覆盖 DEFAULT_REGION

# 目录结构
# ~/projects/
#   .envrc          (共享配置)
#   myapp/
#     .envrc        (项目配置, 引入父级)
#     api/
#       .envrc      (API 专用配置)

常用模式

# 全栈项目 .envrc
cat > .envrc << 'EOF'
export APP_ENV="development"
dotenv .env.development

layout python
PATH_add node_modules/.bin
PATH_add ./scripts

export DATABASE_URL="postgres://localhost:5432/myapp"
export REDIS_URL="redis://localhost:6379"
export API_PORT=3000

watch_file pyproject.toml
watch_file package.json
EOF

# 按环境配置
source_env .envrc.$(hostname)
if [ -f ".envrc.local" ]; then
  source_env .envrc.local
fi

# Docker Compose 项目
export COMPOSE_PROJECT_NAME="myapp"
export COMPOSE_FILE="docker-compose.yaml:docker-compose.dev.yaml"

# 从密钥库读取
export DB_PASSWORD=$(cat /run/secrets/db_password 2>/dev/null || echo "dev")