Suốt mười một phần của loạt Unix, ta đã gặp hết bẫy này tới bẫy khác: quên quote biến làm vỡ tên có dấu cách, rm -rf $var/* với biến rỗng thành rm -rf /*, set -e nuốt lỗi trong pipe, biến chưa gán. Điểm chung của chúng: đều là những lỗi tĩnh — nhìn vào mã là thấy, nhưng con người hay bỏ sót. Tin tốt: có một công cụ đọc script và bắt gần như toàn bộ lớp lỗi đó trước khi bạn chạy — shellcheck. Bài này (phần 12, khép lại loạt Unix) chạy shellcheck thật để thấy nó gói gọn mọi bài học của loạt thành cảnh báo tự động.
Một script "bình thường" đầy bẫy
Đây là kiểu script ai cũng từng viết — trông chạy được, nhưng ẩn ba quả bom:
#!/bin/bash
DIR=$1
rm -rf $DIR/* # biến rỗng -> rm -rf /* ; lại không quote
for f in $(ls $DIR); do # lặp qua output ls -> vỡ với tên có dấu cách
echo "xu ly $f"
done
if [ $COUNT == 0 ]; then # biến chưa gán + không quote
echo "rong"
fi
Mỗi dòng là một bài học của loạt này: quoting (phần 7), rm -rf biến rỗng (phần 7), biến chưa gán (set -u, phần 8), và lặp qua ls (phản mẫu word splitting).

Hình 1: Một script với các bẫy kinh điển; shellcheck là linter tĩnh bắt chúng qua mã SCxxxx kèm gợi ý; bản sửa gom mọi bài học của loạt (set -euo pipefail, quote, ${var:?}, glob thay ls); và dùng exit code trong CI.
Đo thật: shellcheck chỉ mặt từng lỗi
Chạy shellcheck trên script trên, nó không chỉ báo "có lỗi" mà chỉ đúng dòng, đúng mã, kèm gợi ý sửa:

Hình 2: Chạy thật — shellcheck báo SC2115/SC2086 ở dòng rm -rf $DIR/* và SC2045 ở for f in $(ls $DIR); bản sửa cho ra sạch (không cảnh báo); DIR rỗng khiến bản gốc thành rm -rf /* còn bản sửa dừng ngay; exit code 1 (lỗi) / 0 (sạch) cho CI.
Ba cảnh báo thật, mỗi cái là một lớp lỗi:
SC2115:rm -rf $DIR/*có thể nở thành/*nếu$DIRrỗng — gợi ý dùng${var:?}.SC2086: quote biến để tránh word splitting/globbing (bài phần 7).SC2045: đừng lặp qua output củals— dùng glob (for f in "$DIR"/*).
Mỗi mã kèm một link shellcheck.net/wiki/SCxxxx giải thích cặn kẽ vì sao và sửa thế nào — vừa sửa vừa học.
Bản sửa và bằng chứng lỗi thật
Áp mọi gợi ý, bản sửa dùng đúng những gì loạt bài đã dạy: set -euo pipefail, ${1:?...} bắt buộc tham số, ${DIR:?} chống rỗng, glob thay ls. Đo thật: shellcheck trên bản sửa cho ra sạch, không một cảnh báo. Và khác biệt không chỉ là "đẹp mã": chạy với DIR rỗng, bản gốc tạo lệnh rm -rf /* (thảm hoạ, mình chỉ in ra không chạy), còn bản sửa dừng ngay với thông báo DIR: can duong dan. shellcheck đã cảnh báo trước chính xác kịch bản này.
Dùng trong CI: biến cảnh báo thành rào chắn
shellcheck trả exit code — đo thật: script lỗi → 1, script sạch → 0. Nghĩa là bạn cắm được nó vào pipeline: shellcheck *.sh trong CI hoặc pre-commit hook sẽ làm build đỏ khi có lỗi, chặn merge. Cả một lớp bug shell — thứ chỉ lộ ra lúc chạy production lúc nửa đêm — bị bắt tự động ngay khi viết. Đây là cách rẻ nhất để nâng chất lượng script cả đội.
Đánh đổi cần cân nhắc
shellcheck bắt lỗi tĩnh, không bắt lỗi logic. Nó thấy quoting sai, biến chưa gán, cú pháp nguy hiểm — nhưng không biết thuật toán của bạn đúng hay sai, hay lệnh có làm đúng ý định không. Nó là lớp phòng thủ đầu, không thay được đọc mã và test. Một script "shellcheck sạch" vẫn có thể làm sai việc.
Đừng tắt cảnh báo bừa — hiểu rồi hãy bỏ qua. shellcheck cho phép tắt từng mã (# shellcheck disable=SC2086), nhưng lạm dụng là vô hiệu hoá chính công cụ. Chỉ tắt khi bạn thật sự hiểu vì sao trường hợp này an toàn (ví dụ cố ý muốn word splitting), và ghi chú lý do. Mỗi lần tắt là một lời hứa "tôi biết mình đang làm gì".
Cân nhắc chuyển sang ngôn ngữ khác khi script quá lớn. shellcheck giúp shell an toàn hơn nhiều, nhưng shell vẫn có những góc sắc. Khi một script vượt vài trăm dòng, có cấu trúc dữ liệu phức tạp, hay cần test kỹ, Python thường dễ bảo trì và ít bẫy hơn. shellcheck giúp bạn viết shell tốt; biết khi nào không nên dùng shell cũng quan trọng ngang vậy.
Ba ý mang về
- Hầu hết bug shell là lỗi tĩnh bắt được tự động: đo thật, shellcheck chỉ ra
SC2086(quote),SC2115(rm -rfrỗng),SC2045(lặp qua ls) — đúng các bẫy suốt loạt — kèm dòng, mã, và gợi ý sửa. - Bản sửa gom mọi bài học và cho ra sạch:
set -euo pipefail+ quote +${var:?}+ glob → shellcheck không còn cảnh báo, và DIR rỗng khiến bản gốc thànhrm -rf /*còn bản sửa dừng ngay. - Cắm shellcheck vào CI: đo thật exit code 1 (lỗi) / 0 (sạch) — dùng trong pre-commit/pipeline để chặn bug shell trước production; nhưng nhớ nó bắt lỗi tĩnh, không thay đọc mã và test.
Nguồn
- ShellCheck — trang chính và wiki mã lỗi: https://www.shellcheck.net/
- ShellCheck trên GitHub: https://github.com/koalaman/shellcheck
- Google — Shell Style Guide: https://google.github.io/styleguide/shellguide.html
Đến đây khép lại loạt Unix — từ pipe và file descriptor, grep/sed/awk, find/xargs, sort/uniq/cut, quoting, exit code/set -e, tar, quyền chmod, cron, cho tới shellcheck ở bài này. Sợi chỉ xuyên suốt: dòng lệnh Unix không phải phép thuật mà là những công cụ nhỏ, làm một việc thật tốt, ghép lại bằng pipe — và mỗi hành vi "khó hiểu" của chúng đều có lý do đo được. Nắm được lớp này, bạn xử lý dữ liệu nhanh hơn, viết script an toàn hơn, và debug hệ thống với sự tự tin thay vì đoán mò.