ข้ามไปเนื้อหาหลัก

Category: guide

Zsh/Bash Shell Scripting — Automation ใน Terminal

เขียน shell scripts สำหรับ automate งาน: variables, conditions, loops, functions, error handling, และ common patterns

· อ่านประมาณ 5 นาที

สารบัญ

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 $!