cv.cm

Lập trình · Ngôn ngữ lập trình

Học viết script Bash cơ bản cho người mới bắt đầu

8 phút đọc

Trả lời nhanh

Bash là shell dòng lệnh Unix và cũng là ngôn ngữ script, được dùng trên máy chủ Linux, terminal macOS và pipeline CI. Một script Bash là tệp văn bản chứa các lệnh shell, kết hợp với biến, điều kiện, vòng lặp, hàm và pipe. Trang này hướng dẫn viết và chạy script, đặt dấu nháy cho biến an toàn, điều khiển luồng, dùng mảng, chuyển hướng đầu ra và xử lý lỗi bằng set -euo pipefail.

Mở Tính chmod

Bash là gì và dùng để làm gì

Bash (Bourne Again SHell) là shell dòng lệnh và ngôn ngữ script của Dự án GNU. Bash đọc các lệnh bạn gõ, chạy chương trình và nối chúng bằng pipe và chuyển hướng. Script Bash là tệp văn bản chứa chính những lệnh đó, nên mọi việc bạn làm tay trong terminal đều có thể tự động hóa.

Bash là shell tương tác mặc định trên hầu hết bản phân phối Linux và có trên macOS (hiện mặc định dùng zsh nhưng vẫn kèm Bash), cũng như trên Windows qua WSL hoặc Git Bash. Lập trình viên, quản trị hệ thống và kỹ sư DevOps dùng Bash cho script build, triển khai, job CI, bảo trì máy chủ và xử lý dữ liệu nhanh với các công cụ như grep, sed, awk và sort.

Viết và chạy Hello, world

Script bắt đầu bằng dòng shebang cho hệ thống biết dùng trình thông dịch nào. Cấp quyền thực thi cho tệp bằng chmod, rồi chạy nó.

#!/usr/bin/env bash
# hello.sh
echo "Hello, world!"
chmod +x hello.sh
./hello.sh
# Hello, world!
bash hello.sh       # also works without chmod
bash --version      # check which version you have

#!/usr/bin/env bash tìm Bash trong PATH, di động hơn so với việc ghi cứng /bin/bash. Nếu script chỉ dùng tính năng POSIX, có thể dùng #!/bin/sh, nhưng khi đó không được dùng cú pháp chỉ có ở Bash như [[ ]] và mảng.

Biến, dấu nháy và khai triển

Gán bằng name=value và đọc bằng $name hoặc ${name}. Mọi biến đều là chuỗi; Bash chỉ làm số học nguyên bên trong $(( )). Hãy đặt khai triển trong nháy kép để khoảng trắng và ký tự đặc biệt được giữ nguyên.

name="Ada"                 # no spaces around =
count=3
echo "Hello, $name"        # Hello, Ada
echo "Next: $((count + 1))"  # Next: 4  (integer arithmetic)
today=$(date +%Y-%m-%d)    # command substitution
echo "Today is $today"
file="my report.txt"
echo "${file%.txt}.pdf"    # my report.pdf  (strip suffix)
echo "${#name}"            # 3  (string length)
echo "${missing:-default}" # default  (fallback value)
Cú phápÝ nghĩa
"$var"giá trị của biến, giữ thành một từ
'text'văn bản nguyên văn, không khai triển gì cả
$(command)đầu ra của một lệnh
$((a + b))số học nguyên
$1, $2, $@, $#đối số của script hoặc hàm, tất cả đối số, số lượng đối số
$?mã thoát của lệnh cuối cùng (0 nghĩa là thành công)

Điều kiện và case

if chạy một lệnh và kiểm tra mã thoát của nó. Lệnh kiểm tra [[ ]] so sánh chuỗi và kiểm tra tệp; (( )) so sánh số. case so khớp một giá trị với các mẫu glob.

#!/usr/bin/env bash
file="${1:-notes.txt}"

if [[ -f "$file" ]]; then
  echo "$file exists"
elif [[ -d "$file" ]]; then
  echo "$file is a directory"
else
  echo "$file not found"
fi

n=7
if (( n % 2 == 1 )); then echo "$n is odd"; fi

case "$file" in
  *.txt) echo "text file" ;;
  *.sh)  echo "shell script" ;;
  *)     echo "something else" ;;
esac
Kiểm traĐúng khi
[[ -f path ]]đường dẫn là tệp thông thường
[[ -d path ]]đường dẫn là thư mục
[[ -z "$s" ]]chuỗi rỗng
[[ "$a" == "$b" ]]hai chuỗi bằng nhau
(( a > b ))số a lớn hơn số b
[[ $a -lt $b ]]số a nhỏ hơn số b (kiểu cũ)

Vòng lặp

Bash có for trên danh sách các từ, for (( )) kiểu C, while và until. Đọc tệp từng dòng dùng while read.

for fruit in apple banana cherry; do
  echo "fruit: $fruit"
done

for (( i = 1; i <= 3; i++ )); do
  echo "i = $i"
done

for f in *.log; do
  [[ -e "$f" ]] || continue   # skip if no .log files match
  echo "log file: $f"
done

while IFS= read -r line; do
  echo "line: $line"
done < input.txt

IFS= read -r đọc mỗi dòng đúng như đã viết: IFS= giữ khoảng trắng đầu dòng và -r giữ dấu gạch chéo ngược.

Hàm

Định nghĩa hàm bằng name() { ... } và gọi như một lệnh. Đối số đến dưới dạng $1, $2, v.v. Hàm trả về mã thoát (0 đến 255) bằng return; để trả dữ liệu, hãy in ra và bắt đầu ra bằng $( ).

greet() {
  local name="${1:-world}"   # local variable, default value
  echo "Hello, $name!"
}

is_even() {
  (( $1 % 2 == 0 ))          # exit status 0 means true
}

greet                         # Hello, world!
greet "Ann"                   # Hello, Ann!
msg=$(greet "Bo")             # capture output
echo "Got: $msg"              # Got: Hello, Bo!

if is_even 4; then echo "4 is even"; fi

Mảng và mảng kết hợp

Mảng theo chỉ mục chứa danh sách chuỗi. Mảng kết hợp ánh xạ khóa chuỗi sang giá trị và cần Bash 4 trở lên, nên không có trong Bash 3.2 cũ đi kèm macOS.

langs=("bash" "python" "go")
langs+=("rust")
echo "${langs[0]}"           # bash
echo "${#langs[@]}"          # 4
for l in "${langs[@]}"; do echo "- $l"; done

# associative array (Bash 4 or later)
declare -A port
port[http]=80
port[https]=443
echo "${port[https]}"        # 443
for key in "${!port[@]}"; do
  echo "$key -> ${port[$key]}"
done

Luôn viết "${array[@]}" có nháy khi lặp qua các phần tử; cách này giữ mỗi phần tử là một từ ngay cả khi nó chứa khoảng trắng.

Pipe và chuyển hướng

Điểm đặc trưng của Bash là ghép các chương trình nhỏ. Pipe | gửi đầu ra của một lệnh sang đầu vào của lệnh kế tiếp. Chuyển hướng gửi đầu ra vào tệp hoặc đọc đầu vào từ tệp. Mô tả tệp 1 là đầu ra chuẩn và 2 là lỗi chuẩn.

ls -l > files.txt            # write stdout to a file (overwrite)
echo "more" >> files.txt     # append
grep error app.log 2> /dev/null   # discard error messages
make > build.log 2>&1        # stdout and stderr to the same file

# count the 3 most common words in a file
tr -cs '[:alpha:]' '\n' < book.txt | tr '[:upper:]' '[:lower:]' |
  sort | uniq -c | sort -rn | head -n 3

Xử lý lỗi và script an toàn

Mặc định Bash vẫn tiếp tục sau khi một lệnh thất bại. Nhiều script mở đầu bằng set -euo pipefail: -e thoát khi có lỗi không được xử lý, -u coi biến chưa đặt là lỗi, và -o pipefail khiến pipeline thất bại nếu bất kỳ lệnh nào trong đó thất bại. trap chạy mã dọn dẹp khi script thoát.

#!/usr/bin/env bash
set -euo pipefail            # stop on errors, unset vars, pipe failures

tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT     # clean up when the script exits

if ! grep -q "ready" status.txt; then
  echo "status is not ready" >&2   # errors go to stderr
  exit 1
fi

cp config.txt "$tmp" || { echo "copy failed" >&2; exit 2; }
echo "ok"

Dùng || để xử lý lỗi trên một dòng và && để chỉ chạy lệnh khi lệnh trước thành công. set -e có ngoại lệ (ví dụ lệnh trong điều kiện if không kích hoạt nó), nên hãy kiểm tra tường minh các lệnh quan trọng.

Một script hoàn chỉnh nhỏ: sao lưu theo ngày

Script này nén một thư mục thành tệp .tar.gz có dấu thời gian và chỉ giữ năm bản nén mới nhất. Nó dùng đối số, giá trị mặc định, kiểm tra, thay thế lệnh và pipe.

#!/usr/bin/env bash
# backup.sh: copy a directory into a dated .tar.gz archive
set -euo pipefail

usage() { echo "usage: $0 SOURCE_DIR [DEST_DIR]" >&2; exit 1; }

[[ $# -ge 1 ]] || usage
src="$1"
dest="${2:-$HOME/backups}"
[[ -d "$src" ]] || { echo "not a directory: $src" >&2; exit 1; }

mkdir -p "$dest"
stamp=$(date +%Y%m%d-%H%M%S)
archive="$dest/$(basename "$src")-$stamp.tar.gz"

tar -czf "$archive" -C "$(dirname "$src")" "$(basename "$src")"
size=$(du -h "$archive" | cut -f1)
echo "Created $archive ($size)"

# keep only the 5 newest archives for this source
ls -1t "$dest"/"$(basename "$src")"-*.tar.gz | tail -n +6 | while read -r old; do
  rm -f -- "$old"
  echo "Removed $old"
done
./backup.sh ~/projects/site
# Created /home/ann/backups/site-20261031-093000.tar.gz (2.1M)

Học tiếp ở đâu

  • GNU Bash Reference Manual, tài liệu chính thức.
  • man bash và help (ví dụ help read) ngay trong terminal.
  • ShellCheck, công cụ phân tích tĩnh giải thích các lỗi script phổ biến.
  • Đặc tả shell POSIX nếu bạn cần script chạy được cả trong sh, dash hoặc BusyBox.

Câu hỏi thường gặp

Bash và sh khác nhau thế nào?

sh là chuẩn shell POSIX, được triển khai bởi các chương trình như dash. Bash tương thích với POSIX sh nhưng bổ sung các tính năng như [[ ]], mảng, (( )) và khai triển ngoặc nhọn.

Bash và zsh khác nhau thế nào?

Hai shell tương thích với hầu hết lệnh thường ngày. zsh bổ sung nhiều tính năng tương tác và là shell mặc định trên macOS; script viết với shebang bash vẫn chạy bằng Bash.

Vì sao script báo permission denied?

Tệp chưa có quyền thực thi. Hãy chạy chmod +x script.sh, hoặc khởi chạy bằng bash script.sh.

Làm sao truyền đối số cho script Bash?

Viết chúng sau tên script, như ./script.sh one two. Bên trong script chúng là $1 và $2, $@ chứa tất cả và $# là số lượng.

Có nên viết chương trình lớn bằng Bash không?

Bash phù hợp nhất để kết nối các lệnh với nhau. Khi script trở nên phức tạp, cần cấu trúc dữ liệu hoặc xử lý lỗi cẩn thận, một ngôn ngữ như Python thường dễ bảo trì hơn.

Tất cả hướng dẫn lập trình