Bài 01
Shell cơ bản: từ command line đến script đầu tiên
Bài 01 — Shell cơ bản: từ command line đến script đầu tiên
Bạn cài Ubuntu/CentOS, mở terminal, gõ ls thấy danh sách file, nghĩ "OK biết Linux rồi". DevOps thực tế cần viết script tự động hoá. Bài này gói nền: hiểu shell là gì (ranh giới shell vs kernel), viết script đầu tiên, biến, input, calculation, customize môi trường.
Quan trọng: học bash không chỉ là học cú pháp. Đó là cách hiểu Linux từ phía user-space — vì shell là cầu nối duy nhất giữa bạn và kernel.
Linux là gì
Linux là một tập hợp các chương trình và tiện ích được gắn kết với nhau bằng bash shell. Shell quản lý file, dữ liệu, mạng, memory và các tài nguyên khác.
Cấu trúc:
[User]
↓ gõ lệnh
[Shell: bash]
↓ system call
[Kernel]
↓
[CPU, RAM, Disk, Network]Shell
Shell là một chương trình của hệ điều hành. Shell chấp nhận các lệnh con người đọc được và dịch chúng thành thứ kernel đọc và xử lý được.
Shell không phải là một phần của kernel, nhưng sử dụng kernel (qua system call) để thực thi chương trình, tạo file, quản lý job.
Có nhiều shell trên Linux:
- BASH (Bourne-Again SHell) — shell phổ biến nhất, mã nguồn mở, default trên đa số distro.
- CSH (C SHell) — cú pháp giống ngôn ngữ C.
- KSH (Korn SHell) — do David Korn tại AT&T Bell Labs tạo ra. Korn Shell là cơ sở cho POSIX Shell standard.
- TCSH — phiên bản nâng cao và tương thích hoàn toàn với Berkeley UNIX C shell (CSH).
Khi nói "shell script" thường ám chỉ Bash. Bài này dùng Bash.
Dấu nhắc lệnh:
$= user thường.#= root.
Shortcut terminal đáng nhớ
Ctrl+L Clear màn hình
Ctrl+R Search lịch sử lệnh
Ctrl+C Hủy lệnh đang chạy (gửi SIGINT — Bài 03)
Ctrl+W Xoá 1 từ trước con trỏ
Ctrl+U Xoá toàn bộ dòng từ con trỏ về trước
Ctrl+A Về đầu dòng
Ctrl+E Về cuối dòng
Ctrl+T Hoán đổi 2 ký tự cuối trước con trỏ
Tab Auto-complete tên file/lệnh
↑ / ↓ Lịch sử lệnhShell script
Shell script là tệp văn bản chứa một chuỗi lệnh. Thay vì gõ lệnh từng cái, bạn lưu vào file và yêu cầu shell thực thi file đó.
Shell script bao gồm:
- Shell keywords —
if/else,do/while,for,case... - Shell commands (builtins) —
pwd,test,echo,continue,type... - Linux binary commands (external) —
w,who,free,grep,awk... - Text processing — công cụ và lệnh để thao tác, chỉnh sửa, phân tích, xử lý dữ liệu văn bản (
sed,awk,cut,tr). - Functions — các khối mã được đặt tên và có thể tái sử dụng nhiều lần. Giúp tổ chức code tốt hơn và tránh lặp.
- Control flow — câu lệnh
if/then/elsehoặc vòng lặp để thực hiện hành động lặp lại.
Khi nào nên dùng shell script
- Tác vụ lặp đi lặp lại (backup, monitoring, deploy) → tự động hoá.
- Tự động hoá thao tác admin.
- Lấy input từ user/file, xuất ra màn hình hoặc file.
- Tạo tools/utility mạnh cho team.
- Tùy chỉnh tác vụ quản trị hệ thống.
- Vì script được test kỹ, khả năng lỗi giảm khi cấu hình dịch vụ hoặc tác vụ admin (như thêm user mới).
Khi nào KHÔNG dùng bash
- Logic > 500 dòng code → Python/Go.
- Cần data structure phức tạp.
- Network protocol nâng cao.
- Parse JSON/XML chi tiết.
Bash giỏi: thao tác file, process, glue command. Kém: logic phức tạp.
Bash builtins vs external commands — ranh giới shell và OS
Bash có 2 loại lệnh:
Internal commands (builtins)
Builtins là lệnh có sẵn trong shell, bash tự xử lý không cần gọi binary nào ngoài. Bao gồm:
- Lệnh điều khiển shell:
cd,pwd,export,source,alias. - Lệnh kiểm soát luồng:
if,else,elif,while,for,case. - Lệnh quản lý biến:
declare,local,readonly,let. - Lệnh quản lý file/thư mục:
umask,pushd,popd. - Lệnh xử lý job và tiến trình:
jobs,bg,fg,kill,wait. - Lệnh I/O:
echo,printf,read. - Debugging và thông tin:
type,command,help.
Vì sao cd PHẢI là builtins? Nếu cd là external (binary riêng), bash sẽ fork() ra subprocess, subprocess đổi thư mục, subprocess chết → bash parent vẫn ở thư mục cũ. cd không có hiệu lực.
External commands
External commands là các tệp nhị phân riêng biệt được lưu trong các thư mục /sbin, /usr/sbin, /usr/bin, /bin, hoặc /usr/local/bin. Bash phải fork() + exec() để chạy.
type ls
# ls is /bin/ls
type cat
# cat is /usr/bin/catLệnh trùng — bash ưu tiên builtins
Có những lệnh tồn tại ở cả 2 loại, hệ thống ưu tiên dùng builtins, khi cần các tùy chọn nâng cao mới dùng đến external.
type echo
# echo is a shell builtin
ls /bin/echo
# /bin/echo ← cũng có external
echo --version # builtin, không có --version
/bin/echo --version # external, cóHệ quả thực chiến
Script chạy nhiều external trong loop = nhiều fork = chậm. Tránh:
# Chậm — 100 lần fork awk
for i in {1..100}; do
echo "$i" | awk '{print $1*2}'
done
# Nhanh — bash arithmetic builtin
for i in {1..100}; do
echo $(( i * 2 ))
doneLogin shell — file cấu hình
Khi đăng nhập vào hệ thống, các tệp shell script bắt đầu thực thi để thiết lập môi trường và cấu hình cho người dùng.
| File | Mô tả |
|---|---|
/etc/profile |
Thực thi cho tất cả user khi đăng nhập hệ thống |
~/.bash_profile |
Cấu hình cho shell khi user đăng nhập |
~/.profile |
Dùng cho login shell nếu không có ~/.bash_profile |
/etc/bash.bashrc |
Cấu hình cho tất cả user khi mở shell non-login |
~/.bashrc |
Cấu hình cho non-login shell (mở terminal) |
- Login shell (đăng nhập hệ thống):
/etc/profile,~/.bash_profile,~/.profileđược thực thi. - Non-login shell (mở terminal trong môi trường đồ họa):
~/.bashrcđược thực thi.
Vì sao quan trọng cho DevOps? Cron job và systemd chạy script trong non-interactive non-login shell — KHÔNG load .bashrc. Setup PATH, alias trong .bashrc thì cron không thấy.
Fix:
- Set explicit
PATH,HOMEtrong script. - Dùng absolute path (
/usr/bin/pythonthaypython). - Hoặc shebang
#!/bin/bash -lđể force login shell.
Script "Hello World" đầu tiên
vi hello.shNội dung:
#!/bin/bash
echo "Hello, World!"
echo "Knowledge is power."Lưu, thoát. Cấp quyền thực thi + run:
chmod +x hello.sh
./hello.shOutput:
Hello, World!
Knowledge is power.Shebang
Hầu hết các file .sh đều bắt đầu bằng #!/bin/bash hoặc #!/usr/bin/env bash. Cú pháp #! được sử dụng để chỉ định trình thông dịch thực thi script trong UNIX/Linux. Nó được gọi là shebang hoặc dòng "bang".
#!/bin/bash— đường dẫn cố định đến bash.#!/usr/bin/env bash—envtự tìm bash trong$PATH. Portable hơn (hữu ích trên macOS, Alpine container).#= comment (trừ shebang ở dòng đầu).
chmod — phân quyền
chmod +x script.sh # thêm quyền execute
chmod 0755 script.sh # numeric mode
chmod 0700 script.sh # numeric mode khácNumeric mode 4 chữ số: 0 đầu = special bits, sau đó 3 chữ số cho owner / group / others:
4 = read (r)
2 = write (w)
1 = execute (x)
0755 = owner: 7 (rwx) | group: 5 (r-x) | others: 5 (r-x)
0700 = owner: 7 (rwx) | group: 0 (---) | others: 0 (---)
0644 = owner: 6 (rw-) | group: 4 (r--) | others: 4 (r--)
0600 = owner: 6 (rw-) | group: 0 (---) | others: 0 (---)Khi nào dùng cái nào:
- 0755: script/binary chia sẻ — ai cũng đọc/chạy được, chỉ owner sửa.
- 0700: script private (chứa secret) — chỉ owner đọc/chạy.
- 0644: file thường (config, data) — ai cũng đọc, chỉ owner sửa.
- 0600: file private (
.env, key) — chỉ owner.
Mọi script được thực thi bằng:
chmod +x script.sh
./script.shVariables — biến
Biến (variables) là các đối tượng chứa giá trị mà bạn có thể sử dụng trong các lệnh. Biến trong shell giúp lưu trữ và thao tác với dữ liệu như chuỗi, số, tên file, v.v.
Gán và gọi biến
Gán biến (KHÔNG có dấu cách quanh =):
varName=someValueGọi biến — dùng $ theo sau tên biến:
echo "$varName"Ví dụ:
input="/home/sales/data.txt"
echo "Input file $input"
BACKUP="/nas05"
echo "Backing up files to $BACKUP/$USERNAME"
echo "MySHELL=>${SHELL}Code<="Dùng ${var} (có ngoặc) khi cần phân tách rõ với chữ liền kề:
echo "${HOME}work" # in /home/user/work-work
echo "$HOMEwork" # SAI — bash tìm biến HOMEworkQuy tắc đặt tên biến
- Tên biến phải bắt đầu bằng chữ (a-z, A-Z) hoặc dấu gạch dưới (_), không bắt đầu bằng số.
- Tên biến có thể chứa chữ, số, và dấu gạch dưới.
- Tên biến không được chứa dấu cách.
- Tránh dùng từ khóa đặc biệt và tên biến hệ thống (
PATH,HOME,USER). - Tên biến phân biệt chữ hoa và chữ thường.
- Tên ngắn gọn, dễ hiểu.
- Không sử dụng ký tự đặc biệt (dấu chấm, gạch ngang) trong tên biến.
Quote biến — quan trọng nhất bài này
file="my file.txt"
rm $file # SAI — shell hiểu là rm my file.txt (2 file)
rm "$file" # ĐÚNGHabit: luôn quote biến khi expand. Đa số bug script newbie là không quote.
Default value cho tham số
Cú pháp ${var:-default} và ${var:=default}:
die() {
local error="${1:-Undefined error}"
echo "$0: $LINENO $error"
}
die "File not found"
die:-Chỉ sử dụng giá trị mặc định nếu tham số không được cung cấp hoặc trống. Không gán vào biến.:=Gán giá trị mặc định cho biến nếu tham số không được cung cấp hoặc trống, biến sẽ giữ giá trị đó trong suốt phần còn lại của thực thi.
unset — xoá biến
Sử dụng lệnh unset để xoá biến trong khi thực thi chương trình. Có thể xoá cả hàm và biến shell.
vech="Bus"
echo "$vech"
unset vech
echo "$vech" # rỗng
# Xoá function
function hello() { echo "Hello $USER"; }
unset -f helloreadonly — biến hằng
readonly MAX_RETRIES=3
declare -r TIMEOUT=30Sau đó cố gán mới → error. Dùng cho config không bao giờ đổi trong script.
Kiểm tra sự tồn tại của biến
Nếu biến không được định nghĩa, có thể dừng script bằng cú pháp:
${varName?Error varName is not defined}
${varName:?Error varName is not defined or is empty}Ví dụ:
#!/bin/bash
# varcheck.sh
path=${1:?Error command line argument not passed}
echo "Backup path is $path."Nếu không nhập tham số đầu vào → script dừng ngay với lỗi rõ ràng. Đặt đầu mỗi script production cho mọi biến bắt buộc — fail fast, fail loud.
Đọc input từ user
Cấu trúc:
read -p "Prompt" variable1 variable2 ...Ví dụ:
read -r -p "Are you sure (Y/n)? " answer
echo "You typed: $answer"
# Đọc password (không hiện)
read -s -p "Password: " password
echo
# Đọc nhiều biến
read -p "Họ và tên: " ho ten-r để không escape backslash (best practice luôn dùng).
Phép tính số học
Cấu trúc: $((expression))
#!/bin/bash
x=5
y=10
ans=$(( x + y ))
echo "$x + $y = $ans"Toán tử: + - * / % ** (mũ).
Tạo biến số nguyên
declare -i x=10
declare -i y=10
declare -i z=0
z=$(( x + y ))
echo "$x + $y = $z"⚠️ Bash chỉ làm số nguyên. Cần thực dùng bc hoặc awk:
result=$(echo "scale=2; 10/3" | bc) # 3.33
result=$(awk 'BEGIN{ printf "%.2f", 10/3 }')Customize bash environment
Tùy chỉnh môi trường Bash shell liên quan đến việc sửa đổi các tệp cấu hình và thiết lập biến môi trường để điều chỉnh cách Bash hoạt động theo nhu cầu cá nhân. Điều này thường bao gồm:
- Thiết lập biến môi trường
- Tạo alias cho các lệnh
- Định nghĩa hàm shell
- Thay đổi prompt (PS1)
- Tự động thực thi lệnh khi shell khởi động
1. Environment variables
# ~/.bashrc
export EDITOR=vim
export PATH="$HOME/bin:$PATH"
export GOPATH="$HOME/go"
export KUBECONFIG="$HOME/.kube/config"export quan trọng — biến không export thì subprocess không thấy.
2. Alias
alias ll='ls -lah'
alias gst='git status'
alias k='kubectl'
alias kgp='kubectl get pods'
alias dc='docker compose'
alias ..='cd ..'
# Force confirm trước khi xoá
alias rm='rm -i'
alias cp='cp -i'⚠️ Alias chỉ work trong interactive shell, KHÔNG work trong script. Trong script luôn dùng full command.
3. Custom prompt PS1
# Đơn giản
PS1='\u@\h:\w\$ '
# vivek@server:/var/log$
# Có màu + git branch
parse_git_branch() {
git branch 2>/dev/null | grep '^*' | sed 's/* //'
}
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[33m\]$(parse_git_branch)\[\033[00m\]\$ 'Escape sequences:
\uusername\hhostname\wfull path\Wthư mục cuối\$$cho user,#cho root
Production server prompt khác dev — thường có thông tin env (PROD/STAG) màu đỏ để cảnh báo.
4. Shell functions
Function trong .bashrc = command tự định nghĩa, dùng được mọi nơi:
# Tạo + cd vào thư mục
mkcd() {
mkdir -p "$1" && cd "$1"
}
# Extract bất kỳ archive nào
extract() {
case "$1" in
*.tar.gz|*.tgz) tar xzf "$1" ;;
*.tar.bz2) tar xjf "$1" ;;
*.zip) unzip "$1" ;;
*) echo "Unknown: $1" ;;
esac
}
# HTTP server cho thư mục hiện tại
serve() {
python3 -m http.server "${1:-8000}"
}5. Auto-execute khi shell start
Lệnh đặt trong .bashrc chạy mỗi lần mở shell:
# In info hệ thống khi SSH login
if [[ -n "$SSH_CONNECTION" ]]; then
echo "=== $(hostname) ==="
uptime
df -h / | tail -1
fiApply changes sau khi sửa .bashrc:
source ~/.bashrc
# hoặc
. ~/.bashrcCâu chuyện thực tế
Tháng 5/2024, dev báo cron job backup database fail mỗi đêm. Script chạy tay OK, cron fail.
Cron entry:
0 2 * * * /home/devops/scripts/backup.shScript:
#!/bin/bash
mysqldump --defaults-file=~/.my.cnf mydb > backup.sqlChạy tay OK vì $HOME set, ~ expand thành /home/devops. Cron chạy với env tối thiểu — $HOME không set → ~ không expand → mysqldump không tìm thấy config → fail. Stderr không log = silent failure.
Debug bằng dump env của cron:
* * * * * env > /tmp/cron.envSo với env shell tay → thiếu HOME, PATH lệch.
Fix:
#!/bin/bash
HOME="/home/devops"
PATH="/usr/local/bin:/usr/bin:/bin"
cd "$HOME"
mysqldump --defaults-file="$HOME/.my.cnf" mydb > "$HOME/backup.sql" 2>>/var/log/backup.errBài học: Script production không tin env user. Set explicit PATH, HOME, dùng absolute path, redirect stderr về log file. Cron là non-interactive non-login shell — không load .bashrc.
Lab
mkdir /tmp/bash-lab && cd /tmp/bash-lab
# Test builtins vs external
type cd # cd is a shell builtin
type ls # ls is /bin/ls
type echo # echo is a shell builtin
# So sánh tốc độ
time for i in {1..1000}; do echo $(( i * 2 )); done > /dev/null
# Nhanh — bash arithmetic builtin
time for i in {1..1000}; do echo "$i" | awk '{print $1*2}'; done > /dev/null
# Chậm — 1000 lần fork awk
# Script với chmod octal
cat > greet.sh <<'EOF'
#!/bin/bash
read -r -p "Tên: " name
read -r -p "Tuổi: " age
name="${name:-Anonymous}"
current_year=$(date +%Y)
birth_year=$(( current_year - age ))
echo "Chào $name, sinh năm $birth_year"
EOF
chmod 0755 greet.sh
./greet.sh
# Script private
cat > secret.sh <<'EOF'
#!/bin/bash
echo "API_KEY=secret123"
EOF
chmod 0700 secret.sh
ls -l secret.sh # -rwx------
# Default value và bắt buộc
cat > config.sh <<'EOF'
#!/bin/bash
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-5432}"
: "${DB_PASSWORD:?DB_PASSWORD chưa set}"
echo "Connect $DB_HOST:$DB_PORT"
EOF
chmod +x config.sh
./config.sh # fail
DB_PASSWORD="x" ./config.sh # OK
# Customize bash — function
mkcd() { mkdir -p "$1" && cd "$1"; }
mkcd /tmp/test-mkcd
pwd
cd /tmpPitfalls
- Có dấu cách quanh
=khi gán biến → shell hiểu là lệnh.name="x"ĐÚNG,name = "x"SAI. - Quên quote biến
$filekhi tên có dấu cách. Habit:"$file"luôn. - Cron không thấy
.bashrc→ script fail. Set explicit env trong production script. - Phân biệt
:-và:=.:-không gán,:=có gán. - Bash chỉ làm số nguyên. Cần thực dùng
bc/awk. - Alias chỉ work interactive, không work trong script.
- Script chạy nhiều
awk/greptrong loop → nhiều fork, chậm. Ưu tiên builtins.
Tóm tắt
- Linux = chương trình + tiện ích gắn kết bằng bash. Shell = cầu nối user và kernel qua system call.
- Builtins (cd, echo, read) bash tự xử lý — nhanh. External (ls, grep) là binary, fork+exec — chậm hơn nhiều khi gọi liên tục.
- Shebang
#!/bin/bashở dòng đầu mỗi script.chmod 0755cho script chia sẻ,0700cho private. - Biến: gán không dấu cách, gọi
$var. Luôn quote"$var". - Default
${VAR:-default}, bắt buộc${VAR:?error}. - Login vs non-login shell load file khác nhau — cron không load
.bashrc. - Customize
.bashrc: env, alias, PS1, function. Apply bằngsource ~/.bashrc.
Bài tiếp: 02 — Logic và điều khiển: if, case, loop, function.