Skip to content

Biến môi trường và PATH

💡 Hướng dẫn học: Mỗi khi bạn gõ git hoặc python trong terminal, hệ thống phải tìm xem chương trình đó ở đâu. Mỗi khi code của bạn gọi API mô hình ngôn ngữ lớn, chương trình cần biết dùng khóa nào. Hai việc này đều dựa trên cùng một cơ chế — biến môi trường.


0. Mỗi chương trình đều mang theo một bộ cấu hình

Mỗi chương trình đang chạy đều giữ một bộ cấu hình dạng «key=value», gọi là biến môi trường. Chương trình có thể đọc các cấu hình này bất kỳ lúc nào để hiểu môi trường chạy hiện tại.

Nhấn vào bất kỳ biến nào trong danh sách bên dưới để "xem" giá trị của nó trong terminal:

Environment Variable BrowserClick any variable row to inspect its value and purpose in the terminal
VariableExample value
HOME/Users/alice
USERalice
SHELL/bin/zsh
PATH/usr/local/bin:/usr/bin:/bin
PWD/Users/alice/projects
LANGen_US.UTF-8
NODE_ENVdevelopment
OPENAI_API_KEYsk-••••••••••••••••
bash
← Click any variable on the left to inspect it
$
Core concept:Environment variables are key=value configuration owned by each process. A program inherits a copy from its parent process at startup. You can inspect them with echo $VARIABLE and set them with export KEY=value.

1. PATH: Shell tìm lệnh bạn nhập như thế nào

PATH là một biến môi trường đặc biệt, lưu một chuỗi đường dẫn thư mục (phân tách bằng dấu hai chấm). Khi bạn nhập git, Shell sẽ duyệt qua các thư mục theo thứ tự, tìm file thực thi tên git — dừng lại ngay khi tìm thấy cái đầu tiên.

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

Chọn một lệnh, quan sát quá trình Shell tìm kiếm từng thư mục:

PATH Search ProcessEnter a command name and see how the shell searches directories
Choose command:
Current PATH:
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
/usr/local/bin
Waiting
/usr/bin
Waiting
/bin
Waiting
/usr/sbin
Waiting
/sbin
Waiting
Core mechanism:After the shell receives a command name, it searches directories in PATH order. The first match wins and the search stops. That makes PATH order important: earlier directories have higher priority.

Ba quy luật quan trọng:

  • Thư mục nằm càng gần đầu PATH, ưu tiên càng cao
  • Dừng lại khi tìm thấy cái đầu tiên, không tiếp tục tìm kiếm
  • Không có trong tất cả thư mục → command not found

2. Tại sao sau khi cài công cụ phải khởi động lại terminal?

Khi cài các công cụ như nvm, Homebrew, conda, script cài đặt sẽ tự động thêm một dòng vào ~/.zshrc để đưa thư mục của nó vào PATH:

bash
# Nội dung script cài đặt tự ghi (ví dụ)
export PATH="/usr/local/opt/python@3.12/bin:$PATH"

Dòng code này chỉ được thực thi khi Shell mới khởi động. Các cửa sổ terminal đã mở không bị ảnh hưởng, nên:

bash
# Không cần khởi động lại cũng có thể có hiệu lực ngay
source ~/.zshrc

Tình huống phổ biến với công cụ phát triển AI:

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

# Đường dẫn công cụ CLI cài bằng pip (thêm vào PATH)
# macOS: ~/Library/Python/3.x/bin
# Linux: ~/.local/bin
export PATH="$PATH:$HOME/.local/bin"

# Khuyên dùng pipx cài công cụ dòng lệnh, tự quản lý PATH
pipx install aider-chat

3. Phạm vi biến: Ai có thể thấy biến này?

Biến môi trường không được phát sóng cho tất cả các chương trình — mỗi tiến trình giữ bản sao riêng của mình, kế thừa từ tiến trình cha. Sửa bản sao của mình không ảnh hưởng đến tiến trình cha.

Sơ đồ dưới đây thể hiện ba cấp độ. Ở "cấp người dùng", export một biến mới, xem nó có xuất hiện ở "cấp tiến trình" không:

Three Levels of Environment VariablesVariables flow one way from outer scopes to inner scopes; child processes inherit a copy from parents
🖥️
System level /etc/environment
Visible to all users and processes; configured by an administrator
PATH=/usr/local/bin:/usr/bin:/bin
LANG=zh_CN.UTF-8
TZ=Asia/Shanghai
▼ Child process inherits the parent environment
👤
User level ~/.zshrc
Affects only the current user and is loaded when the login shell starts
HOME=/Users/alice
SHELL=/bin/zsh
NVM_DIR=$HOME/.nvm
=
▼ Start a child process, such as node app.js
⚙️
Process level (currently running program)
Inherits variables from upper levels, disappears on exit, and does not modify the parent process
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
One-way flow:Variables are inherited downward only. Changing a variable in a child process does not affect its parent. Variables set with export in a terminal also disappear when that terminal closes.

4. export: Quyết định tiến trình con có đọc được biến này không

Khi đặt biến, có thêm export hay không là hai việc hoàn toàn khác nhau:

export Decides Whether Child Processes Can See a VariableToggle the switch and observe whether the child process can read a variable set by the parent
Parent process (Shell)
$MY_VAR="hello"
$echo $MY_VAR
hello
$bash -c 'echo $MY_VAR'
Start child process
Variable not inherited
Child process (bash -c ...)
$echo $MY_VAR
(empty output)
#A child process cannot modify parent variables
Without export: The variable exists only in the current shell, so the child process reads an empty string.

Để biến tồn tại vĩnh viễn giữa các phiên, ghi export vào file cấu hình:

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. Khóa API: Tuyệt đối không ghi vào code

Khi gọi API OpenAI, Anthropic, DeepSeek, v.v., khóa chính là "CMND + thẻ tín dụng" của bạn. Nếu bị lộ, người khác có thể dùng hạn mức của bạn, chi phí do bạn chịu.

Lỗi phổ biến nhất là ghi khóa trực tiếp vào code:

Hard-coded Keys vs Environment VariablesThe same feature, two implementations, completely different security outcomes
Dangerous: key written in code
# Python
import openai
 
client = openai.OpenAI(
api_key="sk-proj-abc123..."
)
💀After git push, the key is public on GitHub
💀Crawlers can find it quickly and generate costs
💀GitHub Secret Scanner may revoke the key automatically
💀Deleting the commit is not enough because Git history keeps it
Correct: read from environment variable
# Python
import openai, os
 
client = openai.OpenAI(
api_key=os.environ.get("OPENAI_API_KEY")
)
The code contains no secret and can be open-sourced safely
Development, testing, and production can use different keys
If a key leaks, regenerate it without changing code
Team members can use separate keys without affecting each other
Golden rule:A secret string in code means the secret is already leaked. GitHub Secret Scanner can detect prefixes such as sk- shortly after a push and notify providers to revoke them. Even if you delete the commit, Git history still contains it.

6. Phát triển cục bộ: Dùng file .env để quản lý khóa

Trong phát triển cục bộ, đặt khóa vào file .env ở thư mục gốc dự án, code đọc qua thư viện dotenv. .env phải được thêm vào .gitignore, không được đẩy lên Git.

Bên trái viết cấu hình, bên phải đọc — chuyển ngôn ngữ để xem hai cách viết:

.env File + Code ReadingConfiguration on the left, code on the right; the variable name is the only link
📄 .env Do not commit
# Local development config, do not commit to Git
OPENAI_API_KEY=sk-proj-abc123...
DATABASE_URL=postgresql://localhost/dev
PORT=3000
NODE_ENV=development
📋 .env.example Can commit
# Copy to .env and fill in real values
OPENAI_API_KEY=(leave empty)
DATABASE_URL=(leave empty)
PORT=(leave empty)
NODE_ENV=(leave empty)
💻 main.py
# pip install python-dotenv openai
from dotenv import load_dotenv
import os, openai
 
load_dotenv() # Read .env file
 
client = openai.OpenAI(
api_key=os.environ.get("OPENAI_API_KEY")
)
 
db = os.environ.get("DATABASE_URL")
port = int(os.environ.get("PORT", 8000))
Values actually read by the program
OPENAI_API_KEYsk-proj-abc123...
DATABASE_URLpostgresql://localhost/dev
PORT3000
Workflow:load_dotenv() / import 'dotenv/config' reads the .env file at startup, injects its key-value pairs into the process environment, and the code reads them with os.environ or process.env. The two sides are connected only by variable names.

7. Môi trường production: Để nền tảng chạy注入 khóa

.env là công cụ tiện lợi cho giai đoạn phát triển. Trên server và nền tảng đám mây, môi trường chạy nên chịu trách nhiệm注入 khóa, code本身 hoàn toàn không cần biết khóa nằm ở đâu:

How Production Injects Secrets.env is a development convenience; servers should not rely on it
/etc/systemd/system/myapp.service
# Recommended: use a separate secret file with controlled permissions
[Service]
EnvironmentFile=/etc/myapp/secrets.env
ExecStart=/usr/bin/node /app/index.js
# Set file permissions so only the owner can read it
sudo chmod 600 /etc/myapp/secrets.env
sudo chown deploy:deploy /etc/myapp/secrets.env
# Reload configuration and restart the service
sudo systemctl daemon-reload
sudo systemctl restart myapp
After chmod 600, only the deploy user can read the secret file; other accounts cannot access it
Secrets are separated from code, so rotating a key does not require redeploying code
Avoid writing Environment="KEY=val" directly in the systemd file; it requires reloads and leaves plaintext in config
Principle:.env files are convenient for local development. In production, the runtime platform should inject environment variables, while application code stays unaware of where secrets live or how they arrived.

8. Xử lý sự cố thực tế

command not found

bash
# Bước 1: Xác nhận có trong PATH không
which python3         # Có output nghĩa là đã tìm thấy

# Bước 2: Tìm vị trí thực tế của chương trình (macOS)
brew list python | grep bin

# Bước 3: Thêm thư mục vào PATH
export PATH="/đường/dẫn/tìm/được:$PATH"
source ~/.zshrc       # Nhớ source sau khi ghi vào file cấu hình

Đã cài hai version, nhưng không dùng cái mình muốn

bash
which python
# /usr/bin/python ← Phiên bản cũ của hệ thống, nằm trước trong PATH

# Đặt thư mục version mới lên đầu PATH
export PATH="/usr/local/bin:$PATH"

which python
# /usr/local/bin/python ← Version mới, giờ đã được ưu tiên

Biến rõ ràng đã đặt nhưng chương trình không đọc được

Nguyên nhânGiải pháp
Quên exportThêm export rồi thử lại
Sửa ~/.zshrc chưa có hiệu lựcsource ~/.zshrc
Dùng .env nhưng chưa cài dotenvpip install python-dotenv / npm install dotenv
Trên server chỉ hiệu lực trong phiên SSHDùng EnvironmentFile của systemd

Bảng thuật ngữ

Thuật ngữÝ nghĩa
PATHLưu danh sách thư mục Shell tìm file thực thi, phân tách bằng dấu hai chấm, thứ tự quyết định ưu tiên
exportĐánh dấu biến có thể kế thừa, tiến trình con tự động nhận bản sao khi khởi động
sourceThực thi lại file cấu hình trong Shell hiện tại, giúp thay đổi có hiệu lực ngay
whichHiển thị đường dẫn file thực thi tương ứng với lệnh (kết quả tìm kiếm PATH)
.envFile cấu hình cục bộ dự án, lưu khóa dùng khi phát triển, phải thêm vào .gitignore
.env.exampleTemplate tên biến đầy đủ, giá trị để trống, có thể đẩy lên Git an toàn
chmod 600Quyền file: chỉ chủ sở hữu được đọc ghi, phù hợp bảo vệ file khóa
Secret ScannerQuét tự động rò rỉ khóa trên GitHub, phát hiện thì thông báo nhà cung cấp thu hồi