Files
gh-codingkaiser-claude-kais…/skills/shell-scripting/references/patterns.md
2025-11-29 18:15:04 +08:00

9.0 KiB

Common Shell Scripting Patterns

This reference contains frequently-used patterns and solutions for shell scripting challenges.

File and Directory Operations

Check if file/directory exists

if [[ -f "$file" ]]; then
    echo "File exists"
fi

if [[ -d "$dir" ]]; then
    echo "Directory exists"
fi

# Check if file exists and is not empty
if [[ -s "$file" ]]; then
    echo "File exists and has content"
fi

Create directory if it doesn't exist

mkdir -p "$dir"

Find and process files safely

# Method 1: Using find with null delimiter (safest)
while IFS= read -r -d '' file; do
    echo "Processing: $file"
done < <(find . -type f -name "*.txt" -print0)

# Method 2: Using find with -exec
find . -type f -name "*.txt" -exec process_function {} \;

# Method 3: Simple glob (when no subdirectories)
shopt -s nullglob  # Bash only
for file in *.txt; do
    echo "Processing: $file"
done

Read file line by line

# Correct way
while IFS= read -r line; do
    echo "$line"
done < file.txt

# Or with process substitution
while IFS= read -r line; do
    echo "$line"
done < <(command)

Get file information

# File size
size=$(stat -f%z "$file")  # macOS
size=$(stat -c%s "$file")  # Linux

# File modification time
mtime=$(stat -f%m "$file")  # macOS
mtime=$(stat -c%Y "$file")  # Linux

# Portable basename and dirname
filename="${path##*/}"
dirname="${path%/*}"

# File extension
extension="${filename##*.}"
name="${filename%.*}"

String Operations

String manipulation

# Uppercase/lowercase (Bash 4+)
upper="${string^^}"
lower="${string,,}"

# Remove prefix/suffix
no_prefix="${string#prefix}"   # Remove shortest match
no_prefix="${string##prefix}"  # Remove longest match
no_suffix="${string%suffix}"   # Remove shortest match
no_suffix="${string%%suffix}"  # Remove longest match

# Replace substring
new_string="${string/old/new}"   # Replace first occurrence
new_string="${string//old/new}"  # Replace all occurrences

# String length
length="${#string}"

# Substring
substr="${string:start:length}"

Check if string contains substring

if [[ "$string" == *"substring"* ]]; then
    echo "Contains substring"
fi

# Using grep
if echo "$string" | grep -q "pattern"; then
    echo "Contains pattern"
fi

String comparison

# Equality
if [[ "$str1" == "$str2" ]]; then
    echo "Strings are equal"
fi

# Pattern matching
if [[ "$string" == pattern* ]]; then
    echo "Matches pattern"
fi

# Regular expression
if [[ "$string" =~ ^[0-9]+$ ]]; then
    echo "String is numeric"
fi

Array Operations

Array basics

# Create array
arr=("item1" "item2" "item3")

# Add to array
arr+=("item4")

# Access elements
echo "${arr[0]}"      # First element
echo "${arr[@]}"      # All elements
echo "${#arr[@]}"     # Array length

# Iterate over array
for item in "${arr[@]}"; do
    echo "$item"
done

# Iterate with index
for i in "${!arr[@]}"; do
    echo "Index $i: ${arr[$i]}"
done

Check if array contains value

contains() {
    local value=$1
    shift
    local arr=("$@")
    
    for item in "${arr[@]}"; do
        if [[ "$item" == "$value" ]]; then
            return 0
        fi
    done
    return 1
}

if contains "target" "${arr[@]}"; then
    echo "Array contains target"
fi

Process Management

Background processes

# Run in background
command &
pid=$!

# Wait for background process
wait $pid

# Check if process is running
if kill -0 $pid 2>/dev/null; then
    echo "Process is running"
fi

Process timeout

# Using timeout command (GNU coreutils)
timeout 30s command

# Manual implementation
command &
pid=$!
sleep 30
if kill -0 $pid 2>/dev/null; then
    kill $pid
    echo "Command timed out"
fi

Parallel execution

# Using xargs
find . -name "*.txt" -print0 | xargs -0 -P 4 -I {} process_file {}

# Using GNU parallel (if available)
parallel process_file ::: file1 file2 file3

# Manual parallel execution
for file in *.txt; do
    process_file "$file" &
done
wait  # Wait for all background jobs

Input/Output Redirection

Standard redirections

# Redirect stdout to file
command > file

# Redirect stderr to file
command 2> file

# Redirect both stdout and stderr
command &> file
command > file 2>&1

# Append instead of overwrite
command >> file

# Redirect stderr to stdout
command 2>&1

# Discard output
command > /dev/null 2>&1

Here documents

# Basic here document
cat <<EOF
This is a multi-line
string with variable expansion: $var
EOF

# Without variable expansion
cat <<'EOF'
This is a multi-line
string with literal $var
EOF

# Indented here document
cat <<-EOF
	This ignores leading tabs
	Useful for indenting
EOF

Error Handling

Check command exit status

if command; then
    echo "Command succeeded"
else
    echo "Command failed"
fi

# Or store exit code
command
exit_code=$?
if [[ $exit_code -eq 0 ]]; then
    echo "Success"
fi

Robust error handling

set -euo pipefail

# Custom error handler
error_exit() {
    echo "Error: $1" >&2
    exit "${2:-1}"
}

# Usage
[[ -f "$file" ]] || error_exit "File not found: $file" 2

Trap signals

# Cleanup on exit
cleanup() {
    rm -f "$TEMP_FILE"
    echo "Cleanup complete"
}
trap cleanup EXIT

# Handle specific signals
trap 'echo "Interrupted"; exit 130' INT
trap 'echo "Terminated"; exit 143' TERM

# Error line number
trap 'echo "Error on line $LINENO"' ERR

Text Processing

Using grep

# Basic search
grep "pattern" file

# Case-insensitive
grep -i "pattern" file

# Recursive search
grep -r "pattern" directory/

# Show line numbers
grep -n "pattern" file

# Invert match (show non-matching lines)
grep -v "pattern" file

# Extended regex
grep -E "pattern1|pattern2" file

Using sed

# Replace text
sed 's/old/new/' file           # Replace first occurrence per line
sed 's/old/new/g' file          # Replace all occurrences
sed 's/old/new/gi' file         # Case-insensitive replace

# Delete lines
sed '/pattern/d' file           # Delete matching lines
sed '1d' file                   # Delete first line
sed '$d' file                   # Delete last line

# Print specific lines
sed -n '5p' file                # Print line 5
sed -n '5,10p' file             # Print lines 5-10
sed -n '/pattern/p' file        # Print matching lines

# In-place editing
sed -i 's/old/new/g' file       # Linux
sed -i '' 's/old/new/g' file    # macOS

Using awk

# Print specific columns
awk '{print $1, $3}' file

# Filter by column value
awk '$3 > 100' file

# Sum a column
awk '{sum += $1} END {print sum}' file

# Custom field separator
awk -F: '{print $1}' /etc/passwd

# Pattern matching
awk '/pattern/ {print $1}' file

Date and Time

Get current date/time

# Current timestamp
now=$(date +%s)

# Formatted date
date=$(date +"%Y-%m-%d")
datetime=$(date +"%Y-%m-%d %H:%M:%S")

# ISO 8601 format
iso_date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

Date arithmetic

# Days ago (GNU date)
yesterday=$(date -d "yesterday" +%Y-%m-%d)
week_ago=$(date -d "7 days ago" +%Y-%m-%d)

# Days ago (BSD date - macOS)
yesterday=$(date -v-1d +%Y-%m-%d)
week_ago=$(date -v-7d +%Y-%m-%d)

Network Operations

Check if host is reachable

if ping -c 1 -W 1 example.com &> /dev/null; then
    echo "Host is reachable"
fi

Download files

# Using curl
curl -O https://example.com/file
curl -o output.txt https://example.com/file

# Using wget
wget https://example.com/file
wget -O output.txt https://example.com/file

# Follow redirects and show progress
curl -L --progress-bar -o file https://example.com/file

HTTP requests

# GET request
curl https://api.example.com/endpoint

# POST request with JSON
curl -X POST https://api.example.com/endpoint \
    -H "Content-Type: application/json" \
    -d '{"key": "value"}'

# Check HTTP status
status=$(curl -s -o /dev/null -w "%{http_code}" https://example.com)

Temporary Files and Directories

Create temporary files safely

# Create temporary file
TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT

# Create temporary directory
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT

# Create in specific location
TEMP_FILE=$(mktemp /tmp/myapp.XXXXXX)

Miscellaneous

Generate random numbers

# Random number 0-32767
random=$RANDOM

# Random number in range 1-100
random=$((RANDOM % 100 + 1))

# Better random (if available)
random=$(shuf -i 1-100 -n 1)

URL encoding

urlencode() {
    local string="$1"
    local strlen=${#string}
    local encoded=""
    local pos c o
    
    for (( pos=0; pos<strlen; pos++ )); do
        c=${string:$pos:1}
        case "$c" in
            [-_.~a-zA-Z0-9] ) o="$c" ;;
            * ) printf -v o '%%%02x' "'$c" ;;
        esac
        encoded+="$o"
    done
    echo "$encoded"
}

JSON parsing (with jq)

# Extract value
value=$(echo '{"key": "value"}' | jq -r '.key')

# Extract from array
items=$(echo '[{"name": "a"}, {"name": "b"}]' | jq -r '.[].name')

# Create JSON
json=$(jq -n --arg name "value" '{key: $name}')