跳转至

direnv: 按目录自动加载环境变量

为什么要学 direnv

开发者经常需要在不同项目之间切换,每个项目有不同的环境变量(API Key、数据库连接、Python 虚拟环境路径等)。传统做法是手动 source .env 或在 .bashrc 中堆满条件判断。

direnv 的思路极其简单:进入目录时自动加载 .envrc,离开目录时自动卸载

痛点传统方式direnv 方式
多项目环境变量冲突手动 source/unset进出目录自动切换
忘记设置环境变量运行时报错cd 进去就设好了
虚拟环境忘记激活source venv/bin/activate自动激活/退出
.env 安全性容易暴露.envrc 需要显式 direnv allow
嵌套目录环境手动管理自动继承和覆盖

核心概念

白话解释

direnv 是一个 Shell 扩展,它做了一件事:监控你的 cd 命令。当你 cd 到一个包含 .envrc 文件的目录时,它会执行这个文件来设置环境变量。当你 cd 离开时,它会还原所有改动。

整个过程是自动的、安全的(需要你手动批准新的 .envrc 文件)。

核心概念表

概念说明作用
.envrc目录级环境配置文件定义该目录的环境变量
direnv allow批准 .envrc 文件安全机制,防止恶意文件
direnv deny拒绝 .envrc 文件阻止加载
direnv edit编辑并自动批准方便修改
stdlibdirenv 内置的函数库layout python, dotenv
继承子目录继承父目录的环境层级配置
HookShell hook,拦截目录切换实现自动加载/卸载

工作流程

$ cd ~/project-a/           # 进入目录
direnv: loading .envrc       # 自动加载 .envrc
direnv: export +API_KEY +DB_URL  # 设置环境变量

$ echo $API_KEY              # 变量已生效
sk-123456

$ cd ~/project-b/           # 切换到另一个项目
direnv: unloading            # 自动卸载 project-a 的变量
direnv: loading .envrc       # 加载 project-b 的变量
direnv: export +API_KEY +DB_URL  # 不同的值

$ echo $API_KEY              # 变量已切换
sk-789012

$ cd ~                       # 回到 home
direnv: unloading            # 卸载所有项目变量

$ echo $API_KEY              # 变量已清除
(空)

安装配置

安装方式

macOS

brew install direnv

Linux

# Ubuntu/Debian
sudo apt install direnv

# Arch Linux
pacman -S direnv

# 通用安装
curl -sfL https://direnv.net/install.sh | bash

Shell Hook 配置

这一步是必须的——direnv 需要在 Shell 中注入 hook 才能工作。

# Bash — 添加到 ~/.bashrc 末尾
eval "$(direnv hook bash)"

# Zsh — 添加到 ~/.zshrc 末尾
eval "$(direnv hook zsh)"

# Fish — 添加到 ~/.config/fish/config.fish
direnv hook fish | source

重新加载 Shell:

source ~/.bashrc  # 或重新打开终端

验证安装

direnv version
# 2.34.0 或更高

# 创建测试
mkdir /tmp/test-direnv && cd /tmp/test-direnv
echo 'export HELLO=world' > .envrc
# 此时 direnv 会提示需要 allow
direnv allow
echo $HELLO
# → world
cd ..
echo $HELLO
# → (空)

快速上手

基本用法

# 1. 在项目目录创建 .envrc
cd my-project
cat > .envrc << 'EOF'
export DATABASE_URL="postgres://localhost:5432/mydb"
export API_KEY="sk-development-key"
export DEBUG=true
export PORT=3000
EOF

# 2. 批准文件(安全机制)
direnv allow

# 3. 环境变量自动生效
echo $DATABASE_URL
# → postgres://localhost:5432/mydb

# 4. 离开目录时自动卸载
cd ..
echo $DATABASE_URL
# → (空)

加载 .env 文件

很多项目已经有 .env 文件,direnv 可以直接加载:

# .envrc
dotenv
# 等价于加载同目录下的 .env 文件

# 指定其他 .env 文件
dotenv .env.local
# .env
DATABASE_URL=postgres://localhost:5432/mydb
API_KEY=sk-12345
SECRET_KEY=supersecret

使用 direnv edit

# 编辑 .envrc 并自动 allow
direnv edit .
# 打开 $EDITOR 编辑,保存后自动批准

.gitignore 配置

# 将 .envrc 排除出版本控制(包含敏感信息时)
echo ".envrc" >> .gitignore

# 或者提供一个模板
# .envrc.example (提交到 Git)
# .envrc (本地使用,不提交)

进阶用法

stdlib 内置函数

direnv 提供了丰富的内置函数(stdlib):

# 查看所有可用函数
direnv stdlib

常用函数:

# .envrc

# 加载 .env 文件
dotenv

# 加载指定 .env 文件
dotenv_if_exists .env.local

# 添加目录到 PATH
PATH_add bin
PATH_add scripts
PATH_add node_modules/.bin

# Python 虚拟环境
layout python3
# 自动创建并激活 .direnv/python-3.x/

# Node.js (使用 nvm/nodenv)
layout node

# Go (设置 GOPATH)
layout go

# Ruby (使用 rbenv)
layout ruby

# 从父目录继承
source_up

# 记录日志
log_status "Loading project environment"
log_error "Missing required config"

# 条件引入
source_env_if_exists .envrc.local

# 设置变量(如果未设置)
export PORT=${PORT:-3000}

Python 虚拟环境管理

这是 direnv 最流行的用法之一:

# .envrc
layout python3
# 效果:
# 1. 在 .direnv/ 下创建虚拟环境
# 2. cd 进入目录时自动激活
# 3. cd 离开目录时自动退出
# 4. 不需要 source venv/bin/activate!

# 指定 Python 版本
layout python python3.11

# 配合 pyenv
use python 3.11.0
layout pyenv 3.11.0
# 使用示例
cd my-python-project/
# direnv: loading .envrc
# direnv: export +VIRTUAL_ENV ~PATH

pip install flask  # 安装到项目隔离的虚拟环境
python app.py      # 使用项目的 Python

cd ..
# direnv: unloading
# 虚拟环境自动退出

多环境配置

# .envrc(提交到 Git)
# 基础配置 + 加载本地覆盖
export APP_NAME="myapp"
export LOG_LEVEL="info"

# 加载 .env 文件(不提交到 Git)
dotenv_if_exists .env

# 加载本地覆盖(不提交到 Git)
source_env_if_exists .envrc.local
# .envrc.local(本地开发,不提交到 Git)
export DATABASE_URL="postgres://localhost:5432/mydb_dev"
export API_KEY="my-dev-key"
export LOG_LEVEL="debug"  # 覆盖基础配置

嵌套目录继承

project/
├── .envrc           # export APP_NAME="myapp"
├── backend/
│   └── .envrc       # source_up; export PORT=8080
└── frontend/
    └── .envrc       # source_up; export PORT=3000
cd project/          # APP_NAME=myapp
cd project/backend/  # APP_NAME=myapp, PORT=8080
cd project/frontend/ # APP_NAME=myapp, PORT=3000

与工具集成

nvm (Node 版本管理)

# .envrc
use nvm 20
# 或者读取 .nvmrc
use nvm

需要配置 nvm 的 direnv 支持:

# ~/.config/direnv/direnvrc
use_nvm() {
  local node_version=$1
  if [ -z "$node_version" ]; then
    node_version=$(cat .nvmrc 2>/dev/null || echo "")
  fi

  if [ -z "$node_version" ]; then
    log_error "No node version specified"
    return 1
  fi

  local nvm_dir=${NVM_DIR:-$HOME/.nvm}
  local node_path="$nvm_dir/versions/node/v$node_version"

  if [ -d "$node_path" ]; then
    PATH_add "$node_path/bin"
    export NODE_VERSION=$node_version
  else
    log_error "Node $node_version not installed. Run: nvm install $node_version"
  fi
}

mise (版本管理)

# .envrc
use mise
# 自动使用 .mise.toml 中定义的工具版本

Conda

# .envrc — Conda 环境
eval "$(conda shell.bash hook 2>/dev/null)"
conda activate my-env

全局配置

# ~/.config/direnv/direnvrc — 全局函数定义
# 所有项目的 .envrc 都可以使用这里定义的函数

use_pyenv() {
  local python_version=$1
  local pyenv_root=${PYENV_ROOT:-$HOME/.pyenv}
  PATH_add "$pyenv_root/versions/$python_version/bin"
}

# 自定义日志格式
log_status() {
  echo "→ $1"
}
# ~/.config/direnv/direnv.toml — direnv 全局配置
[global]
load_dotenv = false      # 不自动加载 .env
strict_env = false       # 严格模式
warn_timeout = "10s"     # 加载超时警告

[whitelist]
prefix = ["~/projects"]  # 自动信任此前缀下的 .envrc
exact = ["/opt/myapp/.envrc"]

常见问题

Q1: direnv 和 .env 文件有什么区别?

  • .env 文件需要工具(如 Docker、dotenv 库)主动加载
  • .envrc 被 direnv 自动加载/卸载
  • .envrc 可以包含 Shell 脚本逻辑,.env 只是键值对
  • 最佳实践:用 .envrc 调用 dotenv 来加载 .env

Q2: 为什么需要 direnv allow

这是安全机制。如果有人在仓库中提交了恶意的 .envrc(比如偷偷执行命令),direnv 不会自动执行它。你必须显式批准。

Q3: 加载 .envrc 时报 "blocked" 怎么办?

# .envrc 文件被修改后需要重新批准
direnv allow

# 检查当前状态
direnv status

Q4: 如何在 IDE 中使用 direnv?

  • VS Code:安装 "direnv" 扩展
  • JetBrains:安装 "direnv" 插件,或在运行配置中引用 .env
  • Emacsenvrc.el
  • 注意:IDE 内置终端通常已经支持 direnv(因为它是 Shell hook)

Q5: direnv 影响性能吗?

几乎不影响。direnv 只在目录切换时执行,加载一个典型的 .envrc 只需要几毫秒。如果 .envrc 中有耗时操作(如 conda activate),可能会有轻微延迟。

Q6: 如何调试 .envrc 问题?

# 查看当前状态
direnv status

# 显示将要导出的变量
direnv export bash

# 手动重新加载
direnv reload

# 查看 .envrc 的来源
direnv show_dump

参考资源

资源链接
官方网站https://direnv.net
GitHub 仓库https://github.com/direnv/direnv
文档https://direnv.net/#docs
stdlib 函数参考https://direnv.net/man/direnv-stdlib.1.html
Wikihttps://github.com/direnv/direnv/wiki
VS Code 扩展搜索 "direnv"

总结:direnv 是一个小工具,但它解决了一个每天都会遇到的问题——在不同项目之间切换环境变量。安装它、配置 Shell hook、然后在项目中创建 .envrc 文件,就这么简单。它特别适合同时维护多个项目的开发者,以及需要管理 Python 虚拟环境的场景。