Shell Scripting: Tự Động Hóa Bằng Bash

K
Kai··5 min read

Khi bạn gõ cùng một chuỗi lệnh nhiều lần, đã đến lúc viết script. Shell script gom các lệnh (chính những lệnh ta học cả series) thành một file chạy được, thêm biến, điều kiện, vòng lặp — biến thao tác thủ công thành tự động. Đây là kỹ năng biến "biết lệnh" thành "tự động hóa được".

Một script tối thiểu

Script chỉ là một file văn bản chứa các lệnh. Tạo hello.sh:

#!/usr/bin/env bash
echo "Xin chao tu script"

Dòng đầu #!/usr/bin/env bashshebang — nó báo cho hệ thống chạy file này bằng bash. (Có thể viết #!/bin/bash; dùng env bash linh hoạt hơn vì tìm bash theo PATH.)

Cho file quyền chạy (nhớ Bài 7) rồi chạy:

chmod +x hello.sh
./hello.sh

./ cần thiết vì thư mục hiện tại không nằm trong PATH. Cách khác: bash hello.sh (không cần chmod +x).

Biến

name="Nghia"          # KHÔNG có dấu cách quanh dấu =
echo "$name"          # dùng giá trị: thêm $
echo "${name}_dev"    # ngoặc nhọn khi cần tách rõ tên biến

Lỗi kinh điển của người mới: viết name = "Nghia" (có dấu cách) — sai, bash hiểu nhầm là chạy lệnh name. Quy tắc: không dấu cách quanh =.

Luôn để biến trong ngoặc kép khi dùng: "$name". Không có ngoặc kép, biến chứa dấu cách hoặc ký tự đặc biệt sẽ vỡ ra — nguồn của vô số bug script.

Lấy kết quả của một lệnh vào biến (command substitution):

today=$(date +%F)
echo "Hom nay la $today"
files=$(ls | wc -l)

Tham số dòng lệnh

Script nhận tham số khi chạy (./script.sh arg1 arg2):

echo "Tham so 1: $1"        # tham số đầu tiên
echo "Tham so 2: $2"
echo "Tat ca: $@"           # tất cả tham số
echo "So luong: $#"         # số tham số
echo "Ten script: $0"

Đặt giá trị mặc định khi tham số trống:

name="${1:-the gioi}"       # nếu $1 rỗng, dùng "the gioi"

Điều kiện: if

if [ -f /etc/os-release ]; then
  echo "co file"
elif [ -d /tmp ]; then
  echo "co thu muc tmp"
else
  echo "khong co"
fi

[ ... ] là lệnh test. Các kiểm tra hay dùng:

File:    -f (file tồn tại)  -d (thư mục)  -e (tồn tại)  -r/-w/-x (quyền)
Chuỗi:   "$a" = "$b" (bằng)   -z "$a" (rỗng)   -n "$a" (không rỗng)
Số:      -eq (=)  -ne (≠)  -lt (<)  -le (≤)  -gt (>)  -ge (≥)

Lưu ý so sánh số dùng -eq/-lt..., so sánh chuỗi dùng =. (Bash còn có [[ ... ]] mạnh hơn, hỗ trợ &&, ||, so khớp mẫu — nên dùng [[ ]] trong script bash.)

Vòng lặp: for, while

# for: duyệt danh sách
for i in 1 2 3; do
  echo "lan $i"
done

# for: duyệt file (rất hay dùng)
for f in *.txt; do
  echo "xu ly $f"
done

# while: lặp tới khi điều kiện sai
n=0
while [ $n -lt 3 ]; do
  echo "n = $n"
  n=$((n + 1))        # tính toán số học trong $(( ))
done

for f in *.txt kết hợp với wildcard (Bài 3) là mẫu xử lý hàng loạt file rất phổ biến trong script vận hành.

Hàm

Gom logic dùng lại thành hàm:

greet() {
  echo "Xin chao, $1"      # $1 là tham số của HÀM (không phải của script)
}

greet "Linux"
greet "DevOps"

Hàm nhận tham số qua $1, $2... giống script. Đặt hàm trước chỗ gọi.

Exit code: thành công hay thất bại

Mỗi lệnh trả về một exit code: 0 = thành công, khác 0 = lỗi. Đây là cách script biết một lệnh có chạy được không.

ls /tmp
echo $?            # in exit code của lệnh vừa rồi (0 nếu OK)

if grep -q "root" /etc/passwd; then
  echo "tim thay"   # if dựa trên exit code: grep thành công (0) = tìm thấy
fi

&& chạy lệnh sau nếu lệnh trước thành công; || chạy nếu thất bại:

mkdir /tmp/x && echo "tao xong" || echo "tao that bai"

Cho script tự kết thúc với một exit code:

exit 0     # báo thành công
exit 1     # báo lỗi

Viết script an toàn: set -euo pipefail

Đặt dòng này ngay sau shebang trong mọi script nghiêm túc:

#!/usr/bin/env bash
set -euo pipefail
  • -e — dừng ngay khi một lệnh thất bại (thay vì chạy tiếp với trạng thái hỏng). Kiểm chứng: một script set -e gặp ls /khong-ton-tai sẽ dừng tại đó, không chạy các dòng sau.
  • -u — báo lỗi khi dùng biến chưa khai báo (bắt lỗi gõ sai tên biến).
  • -o pipefail — pipeline thất bại nếu bất kỳ lệnh nào trong pipe lỗi (mặc định chỉ xét lệnh cuối).

Ba cờ này biến script "âm thầm chạy tiếp dù hỏng" thành "dừng và báo ngay" — tránh được rất nhiều sự cố khó hiểu. Đây là dấu hiệu của một script viết cẩn thận.

Một script hoàn chỉnh

Ghép lại — script backup một thư mục thành file .tar.gz có ngày tháng (dùng kiến thức Bài 9):

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

src="${1:?Can duong dan thu muc can backup}"   # bắt buộc có tham số 1
dest="/backup"
stamp=$(date +%Y%m%d-%H%M%S)
name="$(basename "$src")-$stamp.tar.gz"

mkdir -p "$dest"
tar -czf "$dest/$name" "$src"
echo "Da backup $src -> $dest/$name"

${1:?thông báo} bắt buộc phải có tham số, không thì script dừng kèm thông báo. Đây là kiểu script bạn sẽ viết nhiều — và Bài 17 sẽ cho nó chạy tự động theo lịch.

🧹 Dọn dẹp

cd /tmp && rm -f hello.sh demo.sh fail.sh

Tổng kết

Shell script gom lệnh thành file chạy được: shebang + chmod +x, biến (không dấu cách quanh =, luôn "$var"), tham số ($1, $@, ${1:-default}), điều kiện (if [[ ]], test file/chuỗi/số), vòng lặp (for, while), hàm, và exit code (0 = OK, $?, &&/||). Luôn mở đầu bằng set -euo pipefail để script dừng khi gặp lỗi. Đây là công cụ biến các lệnh rời rạc thành tự động hóa.

Script viết xong, nhưng vẫn phải chạy tay. Bài cuối (17) cho chúng chạy tự động theo lịch bằng cron — và tổng kết cả series.