Category: guide
Zsh/Bash Shell Scripting — Automation ใน Terminal
เขียน shell scripts สำหรับ automate งาน: variables, conditions, loops, functions, error handling, และ common patterns
สารบัญ
Shebang และ Execution
#!/usr/bin/env bash # portable — ใช้ bash ใดก็ได้ใน PATH
#!/usr/bin/env zsh # เฉพาะ zsh
chmod +x script.sh # ทำให้รันได้
./script.sh # รัน
Variables
# กำหนด (ห้ามมีช่องว่างรอบ =)
name="Alice"
count=42
readonly MAX=100 # ค่าคงที่
# ใช้งาน
echo "$name" # ✓ ใส่ "" กัน word splitting
echo "${name}" # ✓ explicit boundary
echo $name # ✓ แต่อาจมีปัญหาถ้ามี spaces
# String operations
echo "${name:0:3}" # 3 chars จาก index 0 → "Ali"
echo "${name^^}" # uppercase → "ALICE"
echo "${name,,}" # lowercase → "alice"
echo "${#name}" # length → 5
echo "${name/l/L}" # replace first → "ALice"
echo "${name//l/L}" # replace all → "ALLice"
Special Variables
$0 # ชื่อ script
$1 $2 $3 # arguments (positional)
$@ # all arguments (array)
$# # จำนวน arguments
$$ # PID ของ process ปัจจุบัน
$? # exit code ของ command ล่าสุด
$! # PID ของ background process ล่าสุด
Conditions
if [[ "$1" == "hello" ]]; then
echo "Hello!"
elif [[ "$1" == "bye" ]]; then
echo "Goodbye!"
else
echo "Unknown: $1"
fi
# File checks
if [[ -f "$file" ]]; then echo "is a file"; fi
if [[ -d "$dir" ]]; then echo "is a directory"; fi
if [[ -e "$path" ]]; then echo "exists"; fi
if [[ -r "$file" ]]; then echo "readable"; fi
if [[ -z "$var" ]]; then echo "empty string"; fi
if [[ -n "$var" ]]; then echo "non-empty string"; fi
# Number comparisons — ใช้ (( )) หรือ -eq -lt -gt
if (( count > 10 )); then echo "big"; fi
if [[ "$count" -gt 10 ]]; then echo "big"; fi
# Combine conditions
if [[ -f "$file" && -r "$file" ]]; then echo "readable file"; fi
if [[ "$x" == "a" || "$x" == "b" ]]; then echo "a or b"; fi
Loops
# for ใน array
for item in apple banana cherry; do
echo "$item"
done
# for ใน range
for i in {1..10}; do echo "$i"; done
for i in {1..10..2}; do echo "$i"; done # step 2
# C-style for
for (( i=0; i<5; i++ )); do
echo "i=$i"
done
# while
count=0
while (( count < 5 )); do
echo "$count"
(( count++ ))
done
# loop ทุก file ใน folder
for file in src/**/*.ts; do
echo "Processing: $file"
done
# loop จาก command output
while read -r line; do
echo "Line: $line"
done < file.txt
# หรือ
cat file.txt | while read -r line; do
echo "$line"
done
Functions
# กำหนด function
greet() {
local name="$1" # local — ไม่ leak ออก function
local greeting="${2:-Hello}" # default value
echo "$greeting, $name!"
}
# เรียกใช้
greet "Alice" # Hello, Alice!
greet "Bob" "Hi" # Hi, Bob!
# Return value
get_count() {
echo 42 # echo เป็น "return" ของ string
}
count=$(get_count) # capture output
# Exit code
is_valid() {
[[ "$1" =~ ^[0-9]+$ ]] # return 0 ถ้า match, 1 ถ้าไม่
}
if is_valid "123"; then echo "valid"; fi
Error Handling
#!/usr/bin/env bash
set -e # exit immediately on error
set -u # error on unset variable
set -o pipefail # pipeline fails if any command fails
set -x # debug: print each command before running
# Trap errors
cleanup() {
echo "Cleaning up..."
rm -f /tmp/tmpfile
}
trap cleanup EXIT # runs on exit (always)
trap cleanup ERR # runs on error
# Check exit codes explicitly
if ! command -v node &>/dev/null; then
echo "Error: node not found" >&2
exit 1
fi
Arrays
# สร้าง array
fruits=("apple" "banana" "cherry")
files=(src/*.ts) # glob expansion
# ใช้งาน
echo "${fruits[0]}" # apple (bash index starts at 0)
echo "${fruits[@]}" # all elements
echo "${#fruits[@]}" # length = 3
# Add element
fruits+=("date")
# Loop
for f in "${fruits[@]}"; do echo "$f"; done
# Slice
echo "${fruits[@]:1:2}" # banana cherry (index 1, count 2)
Input/Output
# อ่าน user input
read -p "Enter name: " name
read -s -p "Password: " pass # -s = silent (no echo)
# Redirect
command > output.txt # stdout → file (overwrite)
command >> output.txt # stdout → file (append)
command 2> error.txt # stderr → file
command > output.txt 2>&1 # stdout+stderr → file
command &> output.txt # same, shorter
# Pipe
ls -la | grep ".md" | sort
echo "hello" | tr 'a-z' 'A-Z' # uppercase: HELLO
# Here string
grep "foo" <<< "foo bar baz" # pipe string to grep
# Here document
cat <<EOF > config.json
{
"name": "$name",
"version": "1.0"
}
EOF
String Processing
# grep
grep -r "TODO" src/
grep -rn "function" src/*.ts # พร้อม line number
grep -v "test" file.txt # invert match
# awk
awk '{print $2}' file.txt # print column 2
awk -F, '{print $1, $3}' csv.txt # CSV: column 1 and 3
# sed
sed 's/foo/bar/' file.txt # replace first
sed 's/foo/bar/g' file.txt # replace all
sed -i 's/foo/bar/g' file.txt # in-place edit
# cut
echo "a:b:c" | cut -d: -f2 # b
cut -d, -f1,3 data.csv # fields 1 and 3
Common Patterns
# Script ที่รับ args พร้อม usage
usage() {
echo "Usage: $0 [-v] [-o output] <input>"
echo " -v verbose"
echo " -o output file (default: output.txt)"
exit 1
}
verbose=false
output="output.txt"
while getopts "vo:" opt; do
case $opt in
v) verbose=true ;;
o) output="$OPTARG" ;;
*) usage ;;
esac
done
shift $((OPTIND - 1)) # remove parsed options
input="$1"
[[ -z "$input" ]] && usage
$verbose && echo "Processing $input → $output"
# Parallel execution
for url in "${urls[@]}"; do
curl -s "$url" & # background
done
wait # รอทุก background jobs
# Spinner
spinner() {
local frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
local pid=$1
while kill -0 "$pid" 2>/dev/null; do
for frame in $(echo "$frames" | fold -w1); do
printf "\r%s Processing..." "$frame"
sleep 0.1
done
done
printf "\r✓ Done \n"
}
long_command &
spinner $!