Biến (variables) là nền tảng của mọi ngôn ngữ lập trình. Trong Bash, cách khai báo và sử dụng biến có nhiều đặc thù riêng — từ quy tắc quoting nghiêm ngặt đến hệ thống parameter expansion cực kỳ mạnh mẽ. Bài này sẽ giúp bạn nắm vững tất cả.

1. Khai báo biến & Quy tắc đặt tên

Trong Bash, khai báo biến sử dụng cú pháp NAME=value. Lưu ý quan trọng: không có khoảng trắng quanh dấu =.

variables.sh
#!/bin/bash

# ✅ Đúng: không có space quanh =
name="Quang"
age=25
project_dir="/home/user/projects"

# ❌ Sai: có space → Bash hiểu "name" là lệnh, "=" là argument
# name = "Quang"   → bash: name: command not found

# Sử dụng biến: dùng $ hoặc ${}
echo $name              # Quang
echo "Hello, ${name}!"  # Hello, Quang!

# Quy ước đặt tên:
# UPPERCASE → biến môi trường, hằng số: PATH, HOME, DB_HOST
# lowercase → biến cục bộ trong script: file_count, temp_dir
readonly MAX_RETRIES=3   # hằng số (không thể thay đổi)
# MAX_RETRIES=5          # bash: MAX_RETRIES: readonly variable

# Biến môi trường vs biến cục bộ
MY_VAR="local"          # chỉ có trong shell hiện tại
export DB_HOST="localhost"  # biến môi trường, con process kế thừa được
env | grep DB_HOST       # kiểm tra biến môi trường
unset DB_HOST            # xóa biến

2. Quoting: Single vs Double vs Command Substitution

Quoting là một trong những khái niệm quan trọng nhất trong Bash. Mỗi kiểu quote có hành vi khác nhau:

quoting.sh
#!/bin/bash
name="World"

# Single quotes: literal — KHÔNG expand biến, KHÔNG xử lý ký tự đặc biệt
echo 'Hello, $name!'         # Hello, $name!
echo 'Dấu \n không xuống dòng'  # Dấu \n không xuống dòng

# Double quotes: expand biến và command substitution
echo "Hello, $name!"         # Hello, World!
echo "Home dir: $HOME"       # Home dir: /home/user
echo "Ngày hôm nay: $(date)" # Ngày hôm nay: Tue Jun 24 ...

# Command substitution: $(command) — thay thế bằng output của lệnh
current_date=$(date +%Y-%m-%d)
file_count=$(ls | wc -l)
echo "Có $file_count files, ngày $current_date"

# Backtick cũ (tránh dùng, khó đọc khi lồng nhau):
# old_way=`date +%Y-%m-%d`   # tương đương $(date +%Y-%m-%d)

# Escape ký tự đặc biệt trong double quotes
echo "Giá: \$100"            # Giá: $100
echo "Dấu ngoặc: \"xin chào\""  # Dấu ngoặc: "xin chào"

3. Parameter Expansion nâng cao

Đây là tính năng cực kỳ mạnh mẽ giúp xử lý chuỗi mà không cần gọi lệnh bên ngoài (sed, awk):

parameter_expansion.sh
#!/bin/bash

# === Giá trị mặc định ===
echo ${name:-"Guest"}     # Nếu name rỗng/chưa set → dùng "Guest" (không gán)
echo ${name:="Guest"}     # Nếu name rỗng/chưa set → gán "Guest" vào name
echo ${name:+"có giá trị"} # Nếu name CÓ giá trị → trả về "có giá trị"
echo ${name:?"Lỗi: biến name chưa được đặt"}  # Nếu rỗng → báo lỗi và thoát

# === Độ dài chuỗi ===
str="Hello Bash"
echo ${#str}              # 10 (số ký tự)

# === Cắt chuỗi (Substring) ===
echo ${str:0:5}           # Hello (từ vị trí 0, lấy 5 ký tự)
echo ${str:6}             # Bash (từ vị trí 6 đến hết)

# === Trim pattern ===
filepath="/home/user/documents/report.tar.gz"
echo ${filepath#*/}       # home/user/documents/report.tar.gz  (bỏ prefix ngắn nhất match */)
echo ${filepath##*/}      # report.tar.gz  (bỏ prefix dài nhất match */)
echo ${filepath%.*}       # /home/user/documents/report.tar  (bỏ suffix ngắn nhất match .*)
echo ${filepath%%.*}      # /home/user/documents/report  (bỏ suffix dài nhất match .*)

# === Tách tên file và đường dẫn ===
echo ${filepath##*/}      # report.tar.gz  (tên file)
echo ${filepath%/*}       # /home/user/documents  (thư mục cha)

# === Thay thế chuỗi ===
text="foo bar foo baz foo"
echo ${text/foo/FOO}      # FOO bar foo baz foo  (thay thế lần đầu)
echo ${text//foo/FOO}     # FOO bar FOO baz FOO  (thay thế tất cả)
echo ${text/#foo/START}   # START bar foo baz foo (thay thế ở đầu)
echo ${text/%foo/END}     # foo bar foo baz END   (thay thế ở cuối)

# === Chuyển đổi chữ hoa/thường (Bash 4+) ===
word="hello"
echo ${word^}             # Hello (viết hoa ký tự đầu)
echo ${word^^}            # HELLO (viết hoa tất cả)
upper="WORLD"
echo ${upper,}            # wORLD (viết thường ký tự đầu)
echo ${upper,,}           # world (viết thường tất cả)

4. Arrays (Mảng)

arrays.sh
#!/bin/bash

# === Indexed Arrays ===
fruits=("apple" "banana" "cherry" "date")

echo ${fruits[0]}         # apple (phần tử đầu tiên, index bắt đầu từ 0)
echo ${fruits[2]}         # cherry
echo ${fruits[@]}         # apple banana cherry date (tất cả phần tử)
echo ${#fruits[@]}        # 4 (số phần tử)
echo ${!fruits[@]}        # 0 1 2 3 (danh sách index)

# Thêm phần tử
fruits+=("elderberry")    # thêm vào cuối
fruits[10]="fig"          # gán vào index bất kỳ (Bash cho phép sparse array)

# Xóa phần tử
unset fruits[1]           # xóa "banana" (index 1)
echo ${fruits[@]}         # apple cherry date elderberry fig

# Lặp qua mảng
for fruit in "${fruits[@]}"; do
  echo "Quả: $fruit"
done

# Slice (cắt mảng)
echo ${fruits[@]:1:3}     # lấy 3 phần tử từ index 1

# === Associative Arrays (Bash 4+) ===
declare -A config
config[host]="localhost"
config[port]="3306"
config[db]="myapp"

echo ${config[host]}      # localhost
echo ${config[@]}         # tất cả values
echo ${!config[@]}        # tất cả keys: host port db

# Lặp qua associative array
for key in "${!config[@]}"; do
  echo "$key = ${config[$key]}"
done

5. Arithmetic (Phép tính số học)

arithmetic.sh
#!/bin/bash

# === $(( )) — Arithmetic Expansion ===
a=10
b=3

echo $(( a + b ))     # 13
echo $(( a - b ))     # 7
echo $(( a * b ))     # 30
echo $(( a / b ))     # 3 (chia nguyên, không có phần thập phân!)
echo $(( a % b ))     # 1 (phần dư)
echo $(( a ** b ))    # 1000 (lũy thừa: 10^3)

# Gán kết quả
result=$(( (a + b) * 2 ))
echo $result          # 26

# Tăng/giảm
(( a++ ))             # a = 11
(( b-- ))             # b = 2
(( a += 5 ))          # a = 16

# === (( )) — Arithmetic Evaluation (trả về exit code) ===
x=10
if (( x > 5 )); then
  echo "$x lớn hơn 5"    # 10 lớn hơn 5
fi

if (( x % 2 == 0 )); then
  echo "$x là số chẵn"   # 10 là số chẵn
fi

# === let — cách cũ (ít phổ biến) ===
let "c = a + b"
let "c += 10"

# === Phép tính thập phân: dùng bc (Bash không hỗ trợ float) ===
echo "scale=2; 10 / 3" | bc     # 3.33
pi=$(echo "scale=10; 4*a(1)" | bc -l)  # 3.1415926535
echo "Pi = $pi"

6. Câu hỏi trắc nghiệm ôn tập

Trắc nghiệm 1: Quoting

Biến name="Bash". Output của echo 'Hello $name' là gì?

Trắc nghiệm 2: Parameter Expansion

Biến file="report.tar.gz". Output của echo ${file%%.*} là gì?

Trắc nghiệm 3: Arithmetic

Output của echo $(( 10 / 3 )) là gì?

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ề biến, quoting, parameter expansion, arrays và arithmetic:

Tải về variables_strings.sh

Bài viết liên quan trong series

Bài 3: Luồng Điều Khiển — Điều Kiện, Vòng Lặp & Exit Codes Bài 1: Làm Quen Terminal — Filesystem, Lệnh Cơ Bản & Permissions Quay lại Lộ trình Bash Series