Viết Bash script an toàn hơn trên macOS và Linux

Một Bash script tối thiểu:

1
2
3
4
#!/usr/bin/env bash
set -Eeuo pipefail

printf 'Hello %s\n' "${USER:-unknown}"

Chạy:

1
2
chmod u+x script.sh
./script.sh

Hoặc không cần executable bit:

1
bash script.sh

Shebang

1
#!/usr/bin/env bash

env tìm Bash theo PATH, phù hợp khi macOS và Linux đặt Bash khác chỗ. Đổi lại, script phụ thuộc PATH. Với môi trường bị kiểm soát chặt, dùng absolute path có thể dễ đoán hơn.

Strict mode, nhưng hiểu nó

1
set -Eeuo pipefail
  • -e: dừng khi command fail trong phần lớn context.
  • -u: lỗi khi dùng biến chưa set.
  • pipefail: pipeline fail nếu một command bên trong fail.
  • -E: ERR trap được inherit tốt hơn.

Strict mode giảm silent failure, không biến shell thành ngôn ngữ có exception model hoàn hảo.

Quote biến

1
2
3
4
5
rm -- "$file"
for file in "$directory"/*; do
[[ -e "$file" ]] || continue
printf '%s\n' "$file"
done

Không quote biến khiến word splitting và glob expansion can thiệp. Tên file có khoảng trắng là bài test cơ bản; tên file bắt đầu bằng - là bài test xem ta có nhớ -- hay không.

Dùng array thay vì command string

1
2
command=(rsync -av --delete "$source/" "$destination/")
"${command[@]}"

Tránh eval. Nếu dữ liệu bên ngoài chui vào command string, eval có thể biến một filename thành chương trình phụ.

Portability

Bash mặc định trên macOS có thể cũ hơn Bash cài qua Homebrew. Nếu dùng associative array hoặc feature mới, khai báo version requirement và kiểm tra:

1
2
3
4
(( BASH_VERSINFO[0] >= 4 )) || {
printf 'Bash 4+ required\n' >&2
exit 1
}

Dùng ShellCheck trong editor/CI. Nó không viết script thay ta, nhưng bắt được nhiều lỗi trước khi production được quyền tham gia review.