Shell Scripting Best Practices: Error Handling dan Logging
Shell Scripting Best Practices: Error Handling dan Logging
Shell scripting sering dianggap sebagai tools sederhana, namun untuk production environment diperlukan best practices yang ketat. Artikel ini membahas teknik error handling, logging, dan struktur script yang robust.
1. Shebang dan Header Standards
Shebang yang Tepat
#!/bin/bash
# Atau lebih portable:
#!/usr/bin/env bash
# Hindari:
#!/bin/sh # Kurang portable, fitur terbatas
Script Header Template
#!/bin/bash
################################################################################
# Script Name: backup-script.sh
# Description: Automated backup script with rotation
# Author: Your Name
# Date: 2026-02-03
# Version: 1.0.0
# Usage: ./backup-script.sh [source_dir] [dest_dir]
# Dependencies: tar, gzip, logger
################################################################################
set -euo pipefail
IFS=$'\n\t'
2. Strict Mode dengan set Options
Set Options Penting
#!/bin/bash
# Exit immediately jika command gagal
set -e
# Exit jika ada undefined variable
set -u
# Exit jika pipeline gagal (bukan hanya command terakhir)
set -o pipefail
# Kombinasi (best practice)
set -euo pipefail
# Untuk debugging
set -x # Print setiap command sebelum dieksekusi
set +x # Stop debug mode
# Atau kombinasi lengkap:
set -euxo pipefail
Error Handling dengan Trap
#!/bin/bash
set -euo pipefail
# Cleanup function
cleanup() {
local exit_code=$?
echo "Script exiting dengan code: $exit_code"
# Hapapus temporary files
rm -f /tmp/script_*.tmp
# Log exit
logger -t backup-script "Script completed dengan exit code $exit_code"
exit $exit_code
}
# Trap berbagai signals
trap cleanup EXIT
trap 'echo "Error di line $LINENO"' ERR
trap 'echo "Script interrupted"' INT TERM
# Main script logic here
main() {
echo "Processing..."
# Your code
}
main "$@"
3. Error Handling Robust
Function dengan Error Handling
#!/bin/bash
set -euo pipefail
# Function dengan proper error handling
backup_database() {
local db_name="$1"
local backup_dir="${2:-/backup}"
# Validate parameters
if [[ -z "$db_name" ]]; then
log_error "Database name required"
return 1
fi
# Check prerequisites
if ! command -v mysqldump &> /dev/null; then
log_error "mysqldump tidak ditemukan"
return 1
fi
# Create backup directory
if [[ ! -d "$backup_dir" ]]; then
mkdir -p "$backup_dir" || {
log_error "Gagal membuat direktori backup: $backup_dir"
return 1
}
fi
# Perform backup
local backup_file="${backup_dir}/${db_name}_$(date +%Y%m%d_%H%M%S).sql"
if mysqldump -u root -p"$DB_PASSWORD" "$db_name" > "$backup_file"; then
log_info "Backup berhasil: $backup_file"
echo "$backup_file"
return 0
else
log_error "Backup gagal untuk database: $db_name"
return 1
fi
}
# Logging functions
log_info() {
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
log_error() {
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2 | tee -a "$LOG_FILE" >&2
}
log_warn() {
echo "[WARN] $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Usage
if backup_database "mydb" "/backup/db"; then
log_info "Operation completed successfully"
else
log_error "Operation failed"
exit 1
fi
Command Substitution dengan Error Handling
#!/bin/bash
set -euo pipefail
# Simpan output dan exit code
if ! output=$(command_that_might_fail 2>&1); then
echo "Command failed dengan output: $output" >&2
exit 1
fi
# Atau dengan subshell
output=$(command_that_might_fail) || {
echo "Command failed" >&2
exit 1
}
# Multiple commands dengan error handling
{
command1 &&
command2 &&
command3
} || {
echo "One of the commands failed" >&2
exit 1
}
4. Logging yang Professional
Sistem Logging Lengkap
#!/bin/bash
# Konfigurasi logging
readonly LOG_DIR="/var/log/myapp"
readonly LOG_FILE="$LOG_DIR/app-$(date +%Y-%m-%d).log"
readonly LOG_LEVEL="${LOG_LEVEL:-INFO}"
# Log levels
readonly LOG_LEVEL_DEBUG=0
readonly LOG_LEVEL_INFO=1
readonly LOG_LEVEL_WARN=2
readonly LOG_LEVEL_ERROR=3
# Setup logging directory
init_logging() {
if [[ ! -d "$LOG_DIR" ]]; then
mkdir -p "$LOG_DIR" || {
echo "Failed to create log directory: $LOG_DIR" >&2
exit 1
}
fi
}
# Logging functions
log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local script_name=$(basename "$0")
local pid=$$
echo "[$timestamp] [$script_name:$pid] [$level] $message" | tee -a "$LOG_FILE"
# Also log ke syslog jika available
if command -v logger &> /dev/null; then
logger -t "$script_name" "[$level] $message"
fi
}
log_debug() {
[[ $LOG_LEVEL_DEBUG -ge $LOG_LEVEL ]] && log "DEBUG" "$@"
}
log_info() {
[[ $LOG_LEVEL_INFO -ge $LOG_LEVEL ]] && log "INFO" "$@"
}
log_warn() {
[[ $LOG_LEVEL_WARN -ge $LOG_LEVEL ]] && log "WARN" "$@"
}
log_error() {
[[ $LOG_LEVEL_ERROR -ge $LOG_LEVEL ]] && log "ERROR" "$@"
}
# Rotation log
cleanup_old_logs() {
find "$LOG_DIR" -name "app-*.log" -mtime +7 -delete
log_info "Cleaned up old log files"
}
# Usage
init_logging
cleanup_old_logs
log_info "Starting backup process"
log_debug "Debug information: variable=$VAR"
log_warn "Disk space low: 85% used"
log_error "Connection failed to database"
5. Input Validation
Parameter Validation
#!/bin/bash
set -euo pipefail
validate_inputs() {
# Check parameter count
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <source_dir> <backup_dir>" >&2
exit 1
fi
local source_dir="$1"
local backup_dir="$2"
# Validate directories exist
if [[ ! -d "$source_dir" ]]; then
echo "Error: Source directory tidak ditemukan: $source_dir" >&2
exit 1
fi
# Validate not empty
if [[ -z "$source_dir" ]]; then
echo "Error: Source directory cannot be empty" >&2
exit 1
fi
# Validate backup directory writable
if [[ ! -w "$backup_dir" ]]; then
echo "Error: Backup directory tidak writable: $backup_dir" >&2
exit 1
fi
# Validate path tidak mengandung dangerous characters
if [[ "$source_dir" =~ [\;\|\&\$\>\<] ]]; then
echo "Error: Path mengandung karakter invalid" >&2
exit 1
fi
}
# Check required commands
check_dependencies() {
local deps=("tar" "gzip" "logger")
for dep in "${deps[@]}"; do
if ! command -v "$dep" &> /dev/null; then
echo "Error: Required command tidak ditemukan: $dep" >&2
exit 1
fi
done
}
# Main
validate_inputs "$@"
check_dependencies
6. Configuration Management
Config File Loading
#!/bin/bash
# Load konfigurasi dari file
CONFIG_FILE="${CONFIG_FILE:-/etc/myapp/config.conf}"
# Default values
declare -A CONFIG
cat << 'EOF' > "${CONFIG_FILE}.example"
# Default configuration
BACKUP_DIR=/backup
RETENTION_DAYS=7
DB_HOST=localhost
DB_PORT=3306
DB_USER=backup_user
COMPRESS=true
LOG_LEVEL=INFO
EOF
load_config() {
if [[ -f "$CONFIG_FILE" ]]; then
# Source file dengan error handling
if ! source "$CONFIG_FILE" 2>/dev/null; then
echo "Warning: Failed to load config file: $CONFIG_FILE" >&2
return 1
fi
else
echo "Warning: Config file tidak ditemukan, menggunakan defaults" >&2
fi
# Set defaults jika tidak di-set
BACKUP_DIR="${BACKUP_DIR:-/backup}"
RETENTION_DAYS="${RETENTION_DAYS:-7}"
DB_HOST="${DB_HOST:-localhost}"
LOG_LEVEL="${LOG_LEVEL:-INFO}"
}
# Environment variable override
load_env_overrides() {
[[ -n "${APP_BACKUP_DIR:-}" ]] && BACKUP_DIR="$APP_BACKUP_DIR"
[[ -n "${APP_LOG_LEVEL:-}" ]] && LOG_LEVEL="$APP_LOG_LEVEL"
}
load_config
load_env_overrides
Kesimpulan
Shell scripting yang production-ready memerlukan error handling yang robust, logging yang comprehensive, dan validasi input yang ketat. Dengan mengikuti best practices ini, Anda dapat membuat script yang reliable dan maintainable.
Checklist Production Script:
- Gunakan
set -euo pipefail - Implementasikan proper logging
- Validate semua input
- Handle errors gracefully
- Gunakan trap untuk cleanup
- Document dengan komentar
- Test dengan berbagai edge cases
Tools untuk Static Analysis:
shellcheck: Static analysis untuk bash scriptsbashate: Style checker untuk bashshfmt: Auto-formatter untuk shell scripts
# Contoh penggunaan shellcheck
shellcheck your-script.sh
# Install shellcheck
sudo apt install shellcheck
Artikel Terkait
Link Postingan: https://www.tirinfo.com/shell-scripting-best-practices-error-handling-logging/