Lesson 7: Text Processing in Scripts
Goal: Use grep, sed, awk, and string manipulation inside scripts for real data processing.
Table of Contents
- String Manipulation in Bash
- grep in Scripts
- sed in Scripts
- awk in Scripts
- Practical: Log Analyzer
- Practical: Config File Parser
- Exercises
String Manipulation in Bash
Bash has built-in string operations — no external tools needed.
Length, substrings, case
STR="Hello, World!"
echo "${#STR}" # 13 (length)
echo "${STR:0:5}" # Hello (substring)
echo "${STR:7}" # World!
echo "${STR^^}" # HELLO, WORLD! (uppercase)
echo "${STR,,}" # hello, world! (lowercase)
Search and replace
FILE="report-2026-03-22.tar.gz"
echo "${FILE/report/backup}" # backup-2026-03-22.tar.gz
echo "${FILE//-/_}" # report_2026_03_22.tar.gz (all)
Remove prefix / suffix
FILE="report-2026-03-22.tar.gz"
echo "${FILE%.tar.gz}" # report-2026-03-22 (remove suffix)
echo "${FILE%%.*}" # report-2026-03-22 (remove longest suffix)
echo "${FILE#report-}" # 2026-03-22.tar.gz (remove prefix)
echo "${FILE##*/}" # (basename equivalent)
PATH_STR="/home/user/docs/file.txt"
echo "${PATH_STR%/*}" # /home/user/docs (dirname)
echo "${PATH_STR##*/}" # file.txt (basename)
Practical: Process filenames
#!/bin/bash
for file in *.log; do
[[ -f "$file" ]] || continue
base="${file%.log}"
archive="${base}-$(date +%Y%m%d).log.gz"
gzip -c "$file" > "$archive"
echo "Archived: $file → $archive"
done
grep in Scripts
Check if a pattern exists
if grep -q "error" /var/log/syslog; then
echo "Errors found in syslog!"
fi
-q (quiet) suppresses output — just sets exit code.
Extract matching lines
# Get all error lines
ERRORS=$(grep -i "error" /var/log/syslog | tail -5)
echo "$ERRORS"
# Count matches
ERROR_COUNT=$(grep -c "error" /var/log/syslog)
echo "Found $ERROR_COUNT errors"
grep with regex in scripts
#!/bin/bash
# Extract IP addresses from a log file
grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' access.log | sort -u
grep as a filter
# Filter active config lines (no comments, no blanks)
grep -v '^#' /etc/ssh/sshd_config | grep -v '^$'
# Find processes
if ps aux | grep -q "[n]ginx"; then
echo "nginx is running"
fi
sed in Scripts
In-place file editing
#!/bin/bash
CONFIG="/etc/myapp/config.conf"
# Change a setting
sed -i "s/^port=.*/port=8080/" "$CONFIG"
# Enable a commented-out setting
sed -i "s/^#max_connections/max_connections/" "$CONFIG"
# Add a line after a match
sed -i '/\[database\]/a host=localhost' "$CONFIG"
Template processing
#!/bin/bash
# Replace placeholders in a template
APP_NAME="myapp"
APP_PORT="3000"
DB_HOST="localhost"
sed -e "s|{{APP_NAME}}|$APP_NAME|g" \
-e "s|{{APP_PORT}}|$APP_PORT|g" \
-e "s|{{DB_HOST}}|$DB_HOST|g" \
template.conf > output.conf
Extract data with sed
# Extract version number from a file
VERSION=$(sed -n 's/^version=//p' config.ini)
echo "Version: $VERSION"
# Get lines between markers
sed -n '/BEGIN/,/END/p' data.txt
awk in Scripts
Extract and process columns
#!/bin/bash
# Disk space alert
df -h | awk 'NR>1 {
gsub(/%/, "", $5)
if ($5+0 > 80)
printf "WARNING: %s is %s%% full\n", $6, $5
}'
Summarize data
#!/bin/bash
# Sum file sizes in a directory
TOTAL=$(ls -l | awk 'NR>1 {sum += $5} END {print sum}')
echo "Total bytes: $TOTAL"
# Average response time from log
awk '{sum += $NF; count++} END {printf "Avg: %.2f ms\n", sum/count}' access.log
awk as a mini-program
#!/bin/bash
# Parse /etc/passwd and format output
awk -F: '
$3 >= 1000 && $7 != "/usr/sbin/nologin" {
printf "%-15s UID:%-5s Shell:%s\n", $1, $3, $7
}
' /etc/passwd
Practical: Log Analyzer
#!/bin/bash
set -euo pipefail
LOG_FILE="${1:-/var/log/syslog}"
if [[ ! -f "$LOG_FILE" ]]; then
echo "File not found: $LOG_FILE" >&2
exit 1
fi
TOTAL=$(wc -l < "$LOG_FILE")
ERRORS=$(grep -ci "error" "$LOG_FILE" || true)
WARNINGS=$(grep -ci "warn" "$LOG_FILE" || true)
echo "=== Log Analysis: $(basename "$LOG_FILE") ==="
echo "Total lines: $TOTAL"
echo "Errors: $ERRORS"
echo "Warnings: $WARNINGS"
echo ""
if [[ $ERRORS -gt 0 ]]; then
echo "--- Last 5 Errors ---"
grep -i "error" "$LOG_FILE" | tail -5
fi
Practical: Config File Parser
#!/bin/bash
set -euo pipefail
# Parse a simple key=value config file
parse_config() {
local config_file="$1"
if [[ ! -f "$config_file" ]]; then
echo "Config not found: $config_file" >&2
return 1
fi
while IFS='=' read -r key value; do
# Skip comments and empty lines
[[ "$key" =~ ^[[:space:]]*# ]] && continue
[[ -z "$key" ]] && continue
# Trim whitespace
key=$(echo "$key" | xargs)
value=$(echo "$value" | xargs)
echo " $key = $value"
export "$key=$value" 2>/dev/null || true
done < "$config_file"
}
# Usage
cat > /tmp/app.conf << 'EOF'
# Application Config
app_name = MyApp
port = 3000
debug = true
# database
db_host = localhost
db_port = 5432
EOF
echo "=== Parsed Config ==="
parse_config /tmp/app.conf
rm /tmp/app.conf
Exercises
Exercise 1: String manipulation
cat > strings.sh << 'EOF'
#!/bin/bash
set -euo pipefail
URL="https://api.example.com/v2/users?page=1"
echo "Full URL: $URL"
echo "Protocol: ${URL%%://*}"
echo "Domain: $(echo "$URL" | sed 's|https://||; s|/.*||')"
echo "Path: /$(echo "$URL" | cut -d/ -f4-)"
FILENAME="backup-2026-03-22.tar.gz"
echo ""
echo "File: $FILENAME"
echo "Without extension: ${FILENAME%.tar.gz}"
echo "Extension: ${FILENAME##*.}"
echo "Uppercase: ${FILENAME^^}"
EOF
chmod +x strings.sh && ./strings.sh
rm strings.sh
Exercise 2: Process checker
cat > proccheck.sh << 'EOF'
#!/bin/bash
set -euo pipefail
SERVICES=("sshd" "cron" "systemd")
echo "=== Process Status ==="
for svc in "${SERVICES[@]}"; do
PID=$(pgrep -x "$svc" 2>/dev/null | head -1 || true)
if [[ -n "$PID" ]]; then
MEM=$(ps -p "$PID" -o rss= 2>/dev/null | awk '{printf "%.1f MB", $1/1024}')
echo " [RUNNING] $svc (PID: $PID, Mem: $MEM)"
else
echo " [STOPPED] $svc"
fi
done
EOF
chmod +x proccheck.sh && ./proccheck.sh
rm proccheck.sh
Key Takeaways
- Bash built-in string ops (
${var%...},${var//...}) avoid spawning subprocesses grep -qfor silent checks inifconditionssed -ifor in-place editing;sed -efor template processingawkfor column-based data processing and calculations- Combine tools in pipelines for complex text transformations
- Always handle edge cases: empty files, missing patterns, special characters
Next Lesson: Lesson 8: Error Handling & Debugging →