Ngôn Ngữ Kịch Bản Trong Linux (Bash & Shell Scripting)

Cẩm nang Shell Scripting thực chiến cho DevOps & SRE: Strict Mode sống còn, bẫy lỗi với trap, xử lý biến an toàn, template chuẩn Production và công cụ linting tự động.

Trong hệ điều hành Linux, Shell Scripting (phổ biến nhất là Bash) là "chất keo" kết dính các công cụ dòng lệnh lại với nhau. Cho dù bạn dùng Kubernetes, Docker, Terraform hay CI/CD (GitHub Actions, GitLab CI), hầu hết các tác vụ tự động hóa, container entrypoint (docker-entrypoint.sh) và kịch bản triển khai đều được viết bằng Bash.

Tuy nhiên, có một ranh giới rất lớn giữa:

  • Kịch bản tay ngang: Chạy được lúc "trời yên biển lặng", nhưng khi có lỗi mạng hoặc biến bị rỗng thì âm thầm xóa nhầm dữ liệu, nuốt lỗi hoặc treo hệ thống.
  • Kịch bản của Chuyên gia SRE: Bắt buộc có cơ chế phòng thủ (Defensive Programming), tự dừng khi gặp sự cố, luôn dọn dẹp tài nguyên rác trước khi thoát và log rõ ràng từng bước.

🎯 4 Ngôn Ngữ Kịch Bản Phổ Biến Nhất Trên Linux

Ngôn ngữVai trò chínhKhi nào nên dùng?Khi nào KHÔNG nên dùng?
Bash / POSIX shTự động hóa hệ thống, CI/CD, Container EntrypointTác vụ dưới 200 dòng, gọi nhiều lệnh CLI của OSLogic xử lý nghiệp vụ phức tạp, xử lý mảng đa chiều
PythonTự động hóa nâng cao, tương tác Cloud SDK (boto3)Cần gọi REST API phức tạp, phân tích dữ liệu, script > 200 dòngKhi môi trường tối giản (Distroless / Alpine không có Python)
Awk / SedXử lý văn bản, lọc và biến đổi luồng dữ liệu streamTrích xuất cột, thay thế chuỗi regex siêu tốc ngay trên CLIKhông dùng để viết ứng dụng hoàn chỉnh
Go (Single-binary)Công cụ CLI nội bộ, DevOps ToolingCần tốc độ cao, đa luồng, đóng gói 1 file nhị phân chạy ngayTác vụ chỉ cần gọi 2-3 lệnh Linux cơ bản

1. Shebang (#!) & Trình Thông Dịch

Dòng đầu tiên của mọi file script bắt buộc phải là Shebang. Nó chỉ dẫn cho Kernel biết chương trình nào sẽ chịu trách nhiệm thông dịch file này:

#!/usr/bin/env bash

[!TIP] Tại sao nên dùng #!/usr/bin/env bash thay vì #!/bin/bash? Trên một số hệ điều hành (như FreeBSD, macOS hoặc môi trường NixOS), Bash không nằm ở /bin/bash mà ở /usr/local/bin/bash. Dùng /usr/bin/env bash sẽ tự động tìm kiếm Bash theo biến môi trường PATH, giúp script chạy đa nền tảng một cách trơn tru.


2. Quy Tắc Sống Còn: "Bash Strict Mode"

Nếu chỉ được chọn một điều duy nhất cần nhớ về Shell Scripting, hãy luôn đặt dòng này ngay sau dòng Shebang:

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

Tại sao bộ cờ này cứu bạn khỏi những thảm họa Production?

  1. -e (errexit): Dừng script ngay lập tức khi bất kỳ lệnh nào trả về lỗi (exit code khác 0). Mặc định, Bash sẽ lờ đi lệnh lỗi và tiếp tục chạy các dòng sau!

    ⚠️ Thảm họa nếu thiếu -e:

    cd /tmp/backup_folder  # Lệnh này bị lỗi do thư mục chưa tạo!
    rm -rf *                # Vì lệnh trên fail, bạn vẫn đang ở /home hoặc / và XÓA SẠCH MỌI THỨ!
    
  2. -u (nounset): Báo lỗi và dừng script ngay lập tức nếu sử dụng một biến chưa từng được khai báo.

    ⚠️ Thảm họa nếu thiếu -u:

    rm -rf /tmp/$SUBDIR/*   # Nếu $SUBDIR bị rỗng hoặc gõ sai chính tả, lệnh biến thành 'rm -rf /tmp//*'!
    
  3. -o pipefail: Mặc định, trong đường ống cmd1 | cmd2 | cmd3, Bash chỉ quan tâm exit code của lệnh cuối cùng (cmd3). Nếu cmd1 chết nhưng cmd3 chạy được, Bash vẫn xem là thành công! Bật pipefail giúp script fail nếu bất kỳ mắt xích nào trong pipeline gặp sự cố.
  4. IFS=$'\n\t': Đặt ký tự phân tách nội bộ chỉ gồm xuống dòng và tab, ngăn chặn lỗi tách từ khi duyệt file có tên chứa khoảng trắng.

3. Biến & Mở Rộng Biến An Toàn (Variables)

3.1. Luôn bọc biến trong dấu ngoặc kép: "$VAR"

# ❌ Nguy hiểm: Dễ bị lỗi Word Splitting nếu đường dẫn có khoảng trắng
FILE_PATH="/var/log/my app.log"
rm $FILE_PATH     # Bash sẽ hiểu là xóa 2 file: '/var/log/my' và 'app.log'!

# ✅ Chuẩn chuyên gia: Luôn bọc ngoặc kép
rm "$FILE_PATH"

3.2. Giá trị mặc định cho biến (Default Values)

Rất hữu ích khi viết script Docker hoặc CI/CD để cho phép người dùng ghi đè tham số qua biến môi trường:

# Nếu biến APP_PORT chưa có, tự động gán giá trị 8080
PORT="${APP_PORT:-8080}"

# Nếu biến DB_HOST không có, dừng script và in thông báo lỗi
DB_HOST="${1:?Lỗi: Bạn chưa cung cấp DB_HOST làm tham số đầu tiên!}"

3.3. Các tham số đặc biệt (Special Parameters)

BiếnÝ nghĩaVí dụ thực tế
$0Tên của file script đang chạyIn menu hướng dẫn sử dụng
$1, $2, ...Các tham số truyền vào từ dòng lệnh./deploy.sh staging v1.2.0
$#Tổng số lượng tham số truyền vàoif [[ $# -lt 2 ]]; then ...
"$@"Toàn bộ danh sách tham số (giữ nguyên từng phần tử độc lập)Truyền tiếp tham số vào lệnh khác
$?Mã thoát (Exit code) của lệnh chạy gần nhất0 = Thành công, khác 0 = Thất bại
$$PID của chính tiến trình script đang chạyTạo file tạm độc nhất /tmp/app_$$.log

4. Cấu Trúc Điều Khiển Hiện Đại

4.1. Câu lệnh điều kiện: Luôn dùng [[ ... ]] thay vì [ ... ]

Trong Bash hiện đại, cú pháp ngoặc vuông đôi [[ ... ]] là chuẩn vàng:

  • Không bị lỗi cú pháp nếu biến bên trong bị rỗng.
  • Hỗ trợ so khớp mẫu chuỗi và Regex bằng toán tử =~.
  • Dùng được toán tử logic &&, || tự nhiên mà không cần cờ -a, -o.
ENV="production"
VERSION="v2.4.1"

# Kiểm tra logic kết hợp
if [[ "$ENV" == "production" && "$VERSION" =~ ^v[0-9]+\.[0-9]+ ]]; then
    echo "Chuẩn bị deploy phiên bản hợp lệ lên Production..."
fi

# Kiểm tra sự tồn tại của file hoặc thư mục
if [[ -f "/etc/nginx/nginx.conf" ]]; then
    echo "File cấu hình Nginx tồn tại."
fi

if [[ -d "/var/log" ]]; then
    echo "Thư mục /var/log tồn tại."
fi

4.2. Vòng lặp đọc File chuẩn xác (Line-by-line)

Đừng dùng for line in $(cat file.txt) vì nó sẽ bị vỡ dòng nếu có dấu khoảng trắng! Hãy dùng vòng lặp while read:

# Cách đọc từng dòng file chuẩn nhất trong Bash:
while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Đang xử lý mục: $line"
done < "servers.txt"

5. Bẫy Lỗi & Tự Động Dọn Dẹp (trap)

Kịch bản chuyên nghiệp phải biết tự dọn dẹp "bãi rác" nó tạo ra (file tạm, lock file, port kết nối), kể cả khi script bị người dùng bấm Ctrl + C (SIGINT) hoặc đứt gánh giữa chừng.

Lệnh trap cho phép bạn đăng ký một hành động trước khi tiến trình thoát (EXIT):

#!/usr/bin/env bash
set -euo pipefail

# 1. Tạo file tạm thời an toàn
TMP_FILE=$(mktemp /tmp/deploy_payload.XXXXXX)

# 2. Đăng ký bẫy: Dù script thành công hay thất bại, EXIT luôn dọn dẹp file tạm
cleanup() {
    echo "🧹 Đang dọn dẹp tài nguyên tạm thời..."
    rm -f "$TMP_FILE"
}
trap cleanup EXIT

# 3. Thao tác bình thường
echo "Đang ghi dữ liệu vào $TMP_FILE..."
# Nếu có lỗi xảy ra ở đây, hàm cleanup() vẫn tự động được thực thi!

6. Mẫu Kịch Bản Chuẩn Production (Boilerplate)

Dưới đây là khung mẫu (Template) đạt chuẩn vận hành SRE để bạn dùng làm khung sườn cho mọi script tự động hóa:

#!/usr/bin/env bash
# ==============================================================================
# Script Name: deploy_service.sh
# Description: Tự động hóa kiểm tra và triển khai dịch vụ lên máy chủ
# ==============================================================================
set -euo pipefail
IFS=$'\n\t'

# Màu sắc hiển thị terminal
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color

# Hàm ghi log có mốc thời gian chuẩn
log_info()    { echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')] ${GREEN}[INFO]${NC} $*"; }
log_warn()    { echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')] ${YELLOW}[WARN]${NC} $*"; }
log_error()   { echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')] ${RED}[ERROR]${NC} $*" >&2; }

# Hiển thị hướng dẫn
usage() {
    cat << EOF
Sử dụng: $(basename "$0") [-e ENVIRONMENT] [-v VERSION] [-h]

Tùy chọn:
  -e    Môi trường triển khai (staging | production)
  -v    Phiên bản dịch vụ (vd: v1.0.0)
  -h    Hiển thị trợ giúp
EOF
    exit 1
}

# Khởi tạo biến mặc định
ENVIRONMENT=""
VERSION=""

# Phân tích tham số truyền vào bằng getopts
while getopts ":e:v:h" opt; do
    case "$opt" in
        e) ENVIRONMENT="$OPTARG" ;;
        v) VERSION="$OPTARG" ;;
        h) usage ;;
        \?) log_error "Tùy chọn không hợp lệ: -$OPTARG"; usage ;;
        :) log_error "Tùy chọn -$OPTARG bắt buộc phải có giá trị."; usage ;;
    esac
done

# Kiểm tra tham số bắt buộc
if [[ -z "$ENVIRONMENT" || -z "$VERSION" ]]; then
    log_error "Thiếu tham số bắt buộc (-e hoặc -v)."
    usage
fi

main() {
    log_info "Bắt đầu triển khai phiên bản [$VERSION] lên môi trường [$ENVIRONMENT]..."
    
    # Thực hiện logic chính ở đây...
    
    log_info "Triển khai hoàn tất thành công!"
}

main

7. Đảm Bảo Chất Lượng Với ShellCheck

Trong phát triển phần mềm hiện đại, không ai kiểm tra script bằng mắt thường. ShellCheck là công cụ phân tích tĩnh (Static Analysis) số 1 dành cho Shell Scripting:

# Cài đặt trên Ubuntu / Debian
sudo apt-get install -y shellcheck

# Quét kiểm tra script tìm lỗ hổng cú pháp và thói quen xấu
shellcheck deploy_service.sh

[!NOTE] Hãy tích hợp shellcheck vào luồng Pre-commit hook hoặc CI/CD Pipeline để tự động chặn mọi script cẩu thả trước khi được merge vào kho mã nguồn.


⚡ Bảng Tra Cứu Quy Tắc Vàng (SRE Checklist)

Quy tắcCần làmTránh làm
Shebang#!/usr/bin/env bash#!/bin/sh nhưng dùng cú pháp của Bash
Bảo vệ lỗiLuôn có set -euo pipefail ở đầu fileChạy script mặc định không bắt lỗi
Dùng biến"$MY_VAR" (luôn bọc ngoặc kép)$MY_VAR (dễ lỗi khi có khoảng trắng)
Phạm vi biếnKhai báo local var bên trong hàmĐể biến trong hàm tràn ra global scope
Kiểm tra điều kiệnDùng [[ ... ]]Dùng [ ... ] dễ lỗi cú pháp
Dọn dẹp rácDùng trap 'cleanup' EXITĐể lại file tạm rác khi script bị crash
Kiểm tra cú phápDùng shellcheck và bash -nDeploy thẳng lên server rồi chạy thử

Bài tiếp theo đề xuất: Quản Trị Process, Memory & Filesystem – Hiểu sâu vòng đời tiến trình, cơ chế cấp phát bộ nhớ RAM (buff/cache, OOM Killer) và xử lý sự cố Filesystem nâng cao.