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 | 编辑并自动批准 | 方便修改 |
| stdlib | direnv 内置的函数库 | layout python, dotenv 等 |
| 继承 | 子目录继承父目录的环境 | 层级配置 |
| Hook | Shell 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
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:
验证安装¶
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 可以直接加载:
使用 direnv edit¶
.gitignore 配置¶
# 将 .envrc 排除出版本控制(包含敏感信息时)
echo ".envrc" >> .gitignore
# 或者提供一个模板
# .envrc.example (提交到 Git)
# .envrc (本地使用,不提交)
进阶用法¶
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 版本管理)
需要配置 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 (版本管理)
Conda
全局配置¶
# ~/.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" 怎么办?¶
Q4: 如何在 IDE 中使用 direnv?¶
- VS Code:安装 "direnv" 扩展
- JetBrains:安装 "direnv" 插件,或在运行配置中引用
.env - Emacs:
envrc.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 |
| Wiki | https://github.com/direnv/direnv/wiki |
| VS Code 扩展 | 搜索 "direnv" |
总结:direnv 是一个小工具,但它解决了一个每天都会遇到的问题——在不同项目之间切换环境变量。安装它、配置 Shell hook、然后在项目中创建
.envrc文件,就这么简单。它特别适合同时维护多个项目的开发者,以及需要管理 Python 虚拟环境的场景。