跳转至

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

一句话概述:开发者经常需要在不同项目之间切换,每个项目有不同的环境变量(API Key、数据库连接、Python 虚拟环境路径等)。

核心知识点速查表

知识点说明
为什么要学 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
  • 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
Wikihttps://github.com/direnv/direnv/wiki
VS Code 扩展搜索 "direnv"

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