Skip to content

Environment Variables và PATH

Hướng dẫn: mỗi lần bạn gõ git hoặc python trong terminal, hệ thống phải đi tìm program đó ở đâu. Mỗi lần code gọi LLM API, program phải biết dùng key nào. Cả 2 chuyện này dùng cùng 1 cơ chế — environment variable.


0. Mỗi program đều mang theo 1 bộ config

Mỗi process chạy đều giữ 1 bộ "key=value" config, gọi là environment variable. Program có thể đọc bất cứ lúc nào để hiểu môi trường chạy hiện tại.

Click vào bất kỳ variable nào dưới để "xem" value của nó trong terminal:

环境变量浏览器点击任意变量行,在终端中查看它的值和作用
变量名示例值
HOME/Users/alice
USERalice
SHELL/bin/zsh
PATH/usr/local/bin:/usr/bin:/bin
PWD/Users/alice/projects
LANGzh_CN.UTF-8
NODE_ENVdevelopment
OPENAI_API_KEYsk-••••••••••••••••
bash
← 点击左侧任意变量行来查看它
$
核心概念:环境变量是每个进程持有的一组「键=值」配置。程序启动时自动从父进程继承一份,可随时通过 echo $变量名 查看,用 export KEY=value 设置。

1. PATH: Shell tìm command bạn gõ thế nào

PATH là 1 env variable đặc biệt, chứa chuỗi directory path (cách nhau dấu :). Khi bạn gõ git, Shell theo thứ tự chuỗi này, vào từng folder tìm file executable tên git — tìm thấy cái đầu tiên là dừng.

bash
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

Chọn 1 command, xem Shell search từng folder:

PATH 搜索过程输入命令名,看 Shell 是如何逐目录查找的
选择命令:
当前 PATH:
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
/usr/local/bin
待查找
/usr/bin
待查找
/bin
待查找
/usr/sbin
待查找
/sbin
待查找
核心机制:Shell 拿到命令名后,按 PATH 里目录的顺序依次查找。找到第一个匹配就立即使用,停止继续搜索。所以 PATH 中目录的顺序非常重要——先出现的目录优先级更高。

3 quy luật quan trọng:

  • Folder càng đầu PATH, priority càng cao
  • Tìm thấy đầu tiên là dừng, không tiếp tục
  • Hết folder vẫn không thấy → command not found

2. Sao cài tool xong phải restart terminal?

Khi install nvm, Homebrew, conda, script install tự động append vào ~/.zshrc 1 dòng đưa folder vào PATH:

bash
# Script install tự ghi (ví dụ)
export PATH="/usr/local/opt/python@3.12/bin:$PATH"

Dòng này chỉ chạy lúc Shell mới start. Terminal đã mở không bị ảnh hưởng, nên:

bash
# Không cần restart, hiệu lực ngay
source ~/.zshrc

Tình huống thường gặp với AI dev tools:

bash
# Ollama / pipx cài xong báo command not found
which ollama          # check vị trí cài thật

# Tool CLI cài bằng pip (đưa vào PATH)
# macOS: ~/Library/Python/3.x/bin
# Linux: ~/.local/bin
export PATH="$PATH:$HOME/.local/bin"

# Khuyến nghị dùng pipx cho command-line tool, auto-quản lý PATH
pipx install aider-chat

3. Scope của variable: ai thấy được variable này?

Env variable không broadcast tới mọi program — mỗi process giữ bản copy riêng, kế thừa từ parent process. Sửa bản của mình không ảnh hưởng parent.

Hình dưới có 3 cấp. Trong "user-level" export 1 variable mới, xem nó có hiện ở "process-level" không:

环境变量的三个层级变量从外到内单向传递,子进程继承父进程的副本
🖥️
系统级 /etc/environment
所有用户、所有进程都能看到,由管理员配置
PATH=/usr/local/bin:/usr/bin:/bin
LANG=zh_CN.UTF-8
TZ=Asia/Shanghai
▼ 子进程继承父进程环境
👤
用户级 ~/.zshrc
只影响当前用户,登录 Shell 启动时自动加载
HOME=/Users/alice
SHELL=/bin/zsh
NVM_DIR=$HOME/.nvm
=
▼ 启动子进程(如 node app.js)
⚙️
进程级(当前运行的程序)
继承所有上层变量,退出后消失,修改不影响父进程
PATH=/usr/local/bin:/usr/bin:/bin
LANG=zh_CN.UTF-8
TZ=Asia/Shanghai
HOME=/Users/alice
SHELL=/bin/zsh
NVM_DIR=$HOME/.nvm
NODE_ENV=development
PORT=3000
单向传递:变量只能向下继承,子进程修改变量值不会影响父进程。关闭终端后,直接 export 的变量也会消失。

4. export: quyết định subprocess đọc được variable không

Set variable mà không có export ≠ có export:

export 决定子进程能不能"看见"变量切换开关,观察子进程是否能读到父进程设置的变量
父进程(Shell)
$MY_VAR="hello"
$echo $MY_VAR
hello
$bash -c 'echo $MY_VAR'
启动子进程
变量未继承
子进程(bash -c ...)
$echo $MY_VAR
(空,什么都没有)
#子进程无法修改父进程的变量
没有 export: 变量只存在于当前 Shell,子进程读到的是空字符串。

Để variable cross-session vĩnh viễn, ghi export vào file config:

bash
# macOS (zsh)
echo 'export MY_VAR="value"' >> ~/.zshrc
source ~/.zshrc       # Hiệu lực ngay, không cần mở lại terminal

# Linux (bash)
echo 'export MY_VAR="value"' >> ~/.bashrc
source ~/.bashrc

5. API key: TUYỆT ĐỐI không viết vào code

Khi call OpenAI, Anthropic, DeepSeek API, key chính là "CMND + thẻ tín dụng" của bạn. Leak → người khác xài hết quota, hoá đơn bạn trả.

Sai lầm phổ biến nhất là viết key thẳng vào code:

硬编码密钥 vs 用环境变量同样的功能,两种写法,安全性天壤之别
危险写法:密钥写在代码里
# Python
import openai
 
client = openai.OpenAI(
api_key="sk-proj-abc123..."
)
💀git push 后,密钥就公开在 GitHub 上
💀爬虫秒级扫描,密钥被盗用并产生费用
💀GitHub Secret Scanner 自动吊销密钥
💀删除提交也没用,Git 历史仍保留
正确写法:从环境变量读取
# Python
import openai, os
 
client = openai.OpenAI(
api_key=os.environ.get("OPENAI_API_KEY")
)
代码里没有任何密钥信息,可以安全开源
不同环境(开发/测试/生产)用不同密钥
密钥泄露时只需重新生成,不用改代码
团队成员各用各的密钥,互不影响
黄金法则:代码里出现密钥字符串 = 密钥已泄露。GitHub 的 Secret Scanner 会在推送后秒级扫描,发现 sk- 等前缀就通知厂商吊销。即使立刻删除提交,Git 历史里仍然保存着。

6. Local dev: dùng file .env quản lý key

Lúc dev local, để key trong .env ở root project, code đọc qua dotenv lib. .env PHẢI add vào .gitignore, không commit vào Git.

Bên trái viết config, bên phải đọc — switch giữa 2 ngôn ngữ:

.env 文件 + 代码读取左边写配置,右边读取——两者之间只有变量名这一条线
📄 .env 不提交 Git
# 本地开发配置,不提交到 Git
OPENAI_API_KEY=sk-proj-abc123...
DATABASE_URL=postgresql://localhost/dev
PORT=3000
NODE_ENV=development
📋 .env.example 可以提交 Git
# 复制为 .env,填入真实值
OPENAI_API_KEY=(值留空)
DATABASE_URL=(值留空)
PORT=(值留空)
NODE_ENV=(值留空)
💻 main.py
# pip install python-dotenv openai
from dotenv import load_dotenv
import os, openai
 
load_dotenv() # 读取 .env 文件
 
client = openai.OpenAI(
api_key=os.environ.get("OPENAI_API_KEY")
)
 
db = os.environ.get("DATABASE_URL")
port = int(os.environ.get("PORT", 8000))
程序实际读到的值
OPENAI_API_KEYsk-proj-abc123...
DATABASE_URLpostgresql://localhost/dev
PORT3000
工作流程:load_dotenv() / import 'dotenv/config' 在启动时读取 .env 文件,把里面的键值注入到进程环境变量中,代码里再用 os.environprocess.env 读取,两端只靠变量名连接。

7. Production: để platform inject key

.env là tool dev. Trên server và cloud platform, runtime environment chịu trách nhiệm inject key, code hoàn toàn không biết key ở đâu:

生产环境如何注入密钥.env 是开发工具,服务器上不能靠它
/etc/systemd/system/myapp.service
# 推荐:用独立密钥文件,权限可控
[Service]
EnvironmentFile=/etc/myapp/secrets.env
ExecStart=/usr/bin/node /app/index.js
# 设置文件权限:只有所有者可读
sudo chmod 600 /etc/myapp/secrets.env
sudo chown deploy:deploy /etc/myapp/secrets.env
# 应用配置后重启服务
sudo systemctl daemon-reload
sudo systemctl restart myapp
密钥文件 chmod 600 后,只有 deploy 用户可读,其他账号无法访问
密钥和代码完全分离,更新密钥不需要重新部署代码
不要直接在 systemd 文件里写 Environment="KEY=val"——改动需要 reload,且明文在配置里
原则:.env 文件是本地开发便利工具,生产环境应由运行平台负责注入环境变量——代码完全不感知密钥存在哪、怎么来的。

8. Debug thực chiến

command not found

bash
# B1: confirm có trong PATH chưa
which python3         # có output = tìm thấy

# B2: tìm vị trí cài thật (macOS)
brew list python | grep bin

# B3: đưa folder vào PATH
export PATH="/path/found:$PATH"
source ~/.zshrc       # Ghi config xong nhớ source

Cài 2 version, dùng nhầm version

bash
which python
# /usr/bin/python ← bản cũ system, ở đầu PATH

# Đưa folder bản mới ra đầu PATH
export PATH="/usr/local/bin:$PATH"

which python
# /usr/local/bin/python ← bản mới, giờ ưu tiên

Set variable rồi mà program không đọc được

Lý doGiải pháp
Quên exportThêm export thử lại
Sửa ~/.zshrc chưa hiệu lựcsource ~/.zshrc
Dùng .env mà không cài dotenvpip install python-dotenv / npm install dotenv
Trên server chỉ valid trong SSH sessionĐổi sang systemd EnvironmentFile

Glossary

Thuật ngữNghĩa
PATHDanh sách folder Shell tìm executable, cách nhau :, thứ tự = priority
exportMark variable inheritable, subprocess nhận bản copy khi start
sourceRun lại file config trong shell hiện tại, hiệu lực ngay
whichHiện đường dẫn executable cho command (kết quả PATH search)
.envFile config local, chứa key dev, BẮT BUỘC add .gitignore
.env.exampleTemplate với tên variable đủ, value trống, commit Git an toàn
chmod 600Permission: chỉ owner đọc-ghi, dùng cho file key
Secret ScannerGitHub auto scan key leak, phát hiện sẽ báo vendor revoke

2026 cho VN dev

  • AI agents (Claude Code, Cursor) đọc PATH như bạn: cài tool mới mà agent không tìm thấy → restart agent session
  • Multi-environment: dùng direnv auto-load .env theo folder
  • Secret manager: 1Password CLI, Doppler, Infisical thay .env
  • GitHub Actions: dùng Repository Secrets, đừng để key trong workflow YAML
  • macOS Keychain: lưu API key bằng security add-generic-password an toàn hơn .env
  • Cloud-native: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault — production must

Tài liệu