This programming guide is only available in Vietnamese. Switch to Vietnamese to read the full article.
Luồng điều khiển (control flow) cho phép script của bạn đưa ra quyết định và lặp lại các tác vụ. Đây là bước chuyển từ việc gõ lệnh đơn lẻ sang viết chương trình thực sự. Bài này bao gồm exit codes, điều kiện if/case, và tất cả các vòng lặp trong Bash.
1. Exit Codes & $?
Mọi lệnh trong Unix đều trả về một exit code (mã thoát) — một số nguyên từ 0 đến 255.
Quy ước: 0 = thành công, khác 0 = lỗi.
#!/bin/bash
# $? chứa exit code của lệnh vừa chạy
ls /tmp
echo $? # 0 (thành công)
ls /nonexistent_dir
echo $? # 2 (lỗi: không tìm thấy)
# Lệnh true và false
true
echo $? # 0
false
echo $? # 1
# && (AND) — chạy lệnh tiếp theo CHỈ KHI lệnh trước thành công
mkdir mydir && cd mydir && echo "Đã tạo và vào thư mục"
# || (OR) — chạy lệnh tiếp theo CHỈ KHI lệnh trước thất bại
cd /nonexistent || echo "Không thể vào thư mục!"
# Kết hợp && và || (pattern phổ biến)
ping -c 1 google.com &> /dev/null && echo "Online" || echo "Offline"
# Exit codes thường gặp:
# 0 — thành công
# 1 — lỗi chung
# 2 — sai cú pháp / argument
# 126 — file không thể thực thi (permission denied)
# 127 — command not found
# 128+N — bị kill bởi signal N (ví dụ: 130 = Ctrl+C = SIGINT)
2. Điều kiện if/elif/else
Bash có hai cách kiểm tra điều kiện: [ ] (test command truyền thống, POSIX) và
[[ ]] (mở rộng của Bash, hỗ trợ regex, glob, và toán tử logic
&&/||).
#!/bin/bash
# === Cú pháp if cơ bản ===
age=20
if [[ $age -ge 18 ]]; then
echo "Đủ tuổi bầu cử"
elif [[ $age -ge 16 ]]; then
echo "Gần đủ tuổi"
else
echo "Chưa đủ tuổi"
fi
# === So sánh số (dùng trong [ ] và [[ ]]) ===
# -eq (equal), -ne (not equal), -lt (less than)
# -le (less or equal), -gt (greater than), -ge (greater or equal)
if [[ 10 -gt 5 ]]; then echo "10 > 5"; fi
# === So sánh chuỗi ===
name="Bash"
if [[ $name == "Bash" ]]; then echo "Đúng là Bash"; fi
if [[ $name != "Zsh" ]]; then echo "Không phải Zsh"; fi
if [[ -z "$empty_var" ]]; then echo "Biến rỗng hoặc chưa set"; fi # -z: zero length
if [[ -n "$name" ]]; then echo "Biến có giá trị"; fi # -n: non-zero length
# === Kiểm tra file (rất phổ biến trong scripting) ===
if [[ -f "/etc/passwd" ]]; then echo "File tồn tại"; fi # -f: regular file
if [[ -d "$HOME" ]]; then echo "Thư mục tồn tại"; fi # -d: directory
if [[ -e "/tmp" ]]; then echo "Path tồn tại"; fi # -e: exists (file hoặc dir)
if [[ -r "file.txt" ]]; then echo "Có quyền đọc"; fi # -r: readable
if [[ -w "file.txt" ]]; then echo "Có quyền ghi"; fi # -w: writable
if [[ -x "script.sh" ]]; then echo "Có quyền thực thi"; fi # -x: executable
if [[ -s "file.txt" ]]; then echo "File không rỗng"; fi # -s: size > 0
# === Toán tử logic trong [[ ]] ===
if [[ $age -ge 18 && $name == "Bash" ]]; then
echo "Đủ tuổi và tên là Bash"
fi
if [[ $age -lt 10 || $age -gt 60 ]]; then
echo "Quá nhỏ hoặc quá lớn"
fi
# === Regex matching (chỉ trong [[ ]]) ===
email="[email protected]"
if [[ $email =~ ^[a-zA-Z0-9]+@[a-zA-Z]+\.[a-zA-Z]+$ ]]; then
echo "Email hợp lệ"
fi
3. case Statement
Lệnh case thay thế chuỗi if/elif dài dòng khi cần so sánh một biến với nhiều pattern:
#!/bin/bash
# === Menu xử lý input ===
echo "Chọn hành động: (start/stop/restart/status)"
read -r action
case $action in
start)
echo "Đang khởi động service..."
;;
stop)
echo "Đang dừng service..."
;;
restart)
echo "Đang khởi động lại..."
;;
status)
echo "Service đang chạy."
;;
*)
echo "Hành động không hợp lệ: $action"
exit 1
;;
esac
# === Pattern matching với wildcards ===
filename="photo.jpg"
case $filename in
*.jpg|*.jpeg|*.png|*.gif)
echo "Đây là file ảnh"
;;
*.mp4|*.avi|*.mkv)
echo "Đây là file video"
;;
*.sh)
echo "Đây là shell script"
;;
*.tar.gz|*.zip|*.rar)
echo "Đây là file nén"
;;
*)
echo "Loại file không xác định"
;;
esac
# === Case với character class ===
read -rp "Tiếp tục? (y/n): " answer
case $answer in
[Yy]|[Yy][Ee][Ss])
echo "Tiếp tục..."
;;
[Nn]|[Nn][Oo])
echo "Dừng lại."
exit 0
;;
esac
4. Vòng lặp for
#!/bin/bash
# === for in list ===
for color in red green blue; do
echo "Màu: $color"
done
# === for với brace expansion ===
for i in {1..5}; do
echo "Số: $i" # 1, 2, 3, 4, 5
done
for i in {0..20..5}; do
echo "Bước: $i" # 0, 5, 10, 15, 20
done
# === C-style for loop ===
for (( i=0; i<10; i++ )); do
echo "Index: $i"
done
# === Lặp qua file (rất phổ biến) ===
for file in *.txt; do
echo "Đang xử lý: $file"
wc -l "$file" # đếm số dòng
done
# Lặp qua thư mục con
for dir in */; do
echo "Thư mục: $dir"
done
# === Lặp qua output lệnh ===
for user in $(cut -d: -f1 /etc/passwd | head -5); do
echo "User: $user"
done
# === Lặp qua mảng ===
servers=("web01" "web02" "db01" "cache01")
for server in "${servers[@]}"; do
echo "Kiểm tra $server..."
# ping -c 1 "$server" &> /dev/null && echo " OK" || echo " DOWN"
done
5. Vòng lặp while/until & break/continue
#!/bin/bash
# === while loop cơ bản ===
count=1
while [[ $count -le 5 ]]; do
echo "Lần lặp: $count"
(( count++ ))
done
# === until loop (ngược với while: lặp CHO ĐẾN KHI điều kiện đúng) ===
num=1
until [[ $num -gt 5 ]]; do
echo "Until: $num"
(( num++ ))
done
# === Infinite loop + break ===
while true; do
read -rp "Nhập 'quit' để thoát: " input
if [[ $input == "quit" ]]; then
echo "Tạm biệt!"
break
fi
echo "Bạn nhập: $input"
done
# === continue — bỏ qua iteration hiện tại ===
for i in {1..10}; do
if (( i % 2 == 0 )); then
continue # bỏ qua số chẵn
fi
echo "Số lẻ: $i" # 1, 3, 5, 7, 9
done
# === Đọc file line-by-line (best practice) ===
while IFS= read -r line; do
echo "Dòng: $line"
done < /etc/hostname
# Đọc file và xử lý từng dòng
line_num=0
while IFS= read -r line; do
(( line_num++ ))
# Bỏ qua dòng trống và comment
[[ -z "$line" || "$line" =~ ^# ]] && continue
echo "$line_num: $line"
done < config.txt
# === Đọc từ pipe (chú ý: chạy trong subshell!) ===
echo -e "apple\nbanana\ncherry" | while read -r fruit; do
echo "Quả: $fruit"
done
# === while với counter để retry ===
max_retries=3
attempt=1
while [[ $attempt -le $max_retries ]]; do
echo "Lần thử $attempt/$max_retries..."
# Giả sử lệnh thất bại:
if ping -c 1 example.com &> /dev/null; then
echo "Thành công!"
break
fi
(( attempt++ ))
sleep 2
done
if [[ $attempt -gt $max_retries ]]; then
echo "Thất bại sau $max_retries lần thử."
fi
6. Câu hỏi trắc nghiệm ôn tập
Trắc nghiệm 1: Exit Codes
Lệnh false && echo "A" || echo "B" sẽ in ra gì?
Trắc nghiệm 2: Test operators
Toán tử nào kiểm tra xem file có tồn tại VÀ là regular file (không phải thư mục)?
Trắc nghiệm 3: Đọc file
Cách nào là best practice để đọc file line-by-line trong Bash?
Tải file code thực hành minh họa bài học
File script tổng hợp các ví dụ về exit codes, điều kiện, case, và vòng lặp:
Tải về control_flow.sh
Comments
Bình luận