1#!/usr/bin/env bash
2set -euo pipefail
3
4# Error Handling Framework
5# Demonstrates reusable error handling patterns for robust shell scripts
6
7# ============================================================================
8# Color definitions for output
9# ============================================================================
10
11readonly RED='\033[0;31m'
12readonly GREEN='\033[0;32m'
13readonly YELLOW='\033[1;33m'
14readonly BLUE='\033[0;34m'
15readonly CYAN='\033[0;36m'
16readonly MAGENTA='\033[0;35m'
17readonly NC='\033[0m' # No Color
18
19# ============================================================================
20# Error Codes
21# ============================================================================
22
23readonly E_SUCCESS=0
24readonly E_GENERIC=1
25readonly E_INVALID_ARG=2
26readonly E_FILE_NOT_FOUND=3
27readonly E_PERMISSION_DENIED=4
28readonly E_NETWORK_ERROR=5
29readonly E_TIMEOUT=6
30readonly E_DEPENDENCY_MISSING=7
31
32# ============================================================================
33# Error Handling Functions
34# ============================================================================
35
36# Print error message and exit with code
37die() {
38 local message="$1"
39 local exit_code="${2:-$E_GENERIC}"
40
41 echo -e "${RED}ERROR:${NC} $message" >&2
42 exit "$exit_code"
43}
44
45# Print warning message without exiting
46warn() {
47 local message="$1"
48 echo -e "${YELLOW}WARNING:${NC} $message" >&2
49}
50
51# Print info message
52info() {
53 local message="$1"
54 echo -e "${CYAN}INFO:${NC} $message"
55}
56
57# Print success message
58success() {
59 local message="$1"
60 echo -e "${GREEN}SUCCESS:${NC} $message"
61}
62
63# ============================================================================
64# Stack Trace on Error
65# ============================================================================
66
67# Print stack trace
68print_stack_trace() {
69 local frame=0
70 local line_no
71 local func_name
72 local source_file
73
74 echo -e "${MAGENTA}Stack trace:${NC}" >&2
75
76 # Skip the first frame (this function itself)
77 for ((frame=1; frame < ${#FUNCNAME[@]}; frame++)); do
78 func_name="${FUNCNAME[$frame]}"
79 source_file="${BASH_SOURCE[$frame]}"
80 line_no="${BASH_LINENO[$((frame-1))]}"
81
82 echo -e " ${CYAN}[$frame]${NC} $func_name at ${source_file}:${line_no}" >&2
83 done
84}
85
86# Error handler that shows context
87error_handler() {
88 local line_no="$1"
89 local bash_lineno="$2"
90 local func_name="${3:-main}"
91 local command="$4"
92 local error_code="${5:-1}"
93
94 echo >&2
95 echo -e "${RED}╔════════════════════════════════════════════╗${NC}" >&2
96 echo -e "${RED}║ ERROR OCCURRED ║${NC}" >&2
97 echo -e "${RED}╚════════════════════════════════════════════╝${NC}" >&2
98 echo >&2
99 echo -e "${RED}Error Code:${NC} $error_code" >&2
100 echo -e "${RED}Function:${NC} $func_name" >&2
101 echo -e "${RED}Line:${NC} $bash_lineno" >&2
102 echo -e "${RED}Command:${NC} $command" >&2
103 echo >&2
104
105 print_stack_trace
106
107 echo >&2
108}
109
110# Setup ERR trap for automatic error context
111setup_error_trap() {
112 set -eE # Inherit ERR trap in functions
113
114 # Note: BASH_COMMAND contains the command that triggered the error
115 trap 'error_handler ${LINENO} ${BASH_LINENO[0]} "${FUNCNAME[1]}" "$BASH_COMMAND" $?' ERR
116}
117
118# ============================================================================
119# Try/Catch Simulation Using Subshells
120# ============================================================================
121
122# Try to execute a command, catch errors
123# Returns: 0 if success, error code if failure
124try() {
125 local error_code=0
126
127 # Execute in subshell to isolate errors
128 (
129 set -e
130 "$@"
131 ) || error_code=$?
132
133 return $error_code
134}
135
136# Execute catch block if try failed
137catch() {
138 local error_code=$?
139
140 if [[ $error_code -ne 0 ]]; then
141 "$@" "$error_code"
142 fi
143
144 return 0
145}
146
147# ============================================================================
148# Demo Functions
149# ============================================================================
150
151# Function that simulates an error
152function_that_fails() {
153 local depth="${1:-1}"
154
155 if [[ $depth -gt 1 ]]; then
156 nested_function_call $((depth - 1))
157 else
158 info "About to trigger an error..."
159 # This will fail and trigger error handler
160 false
161 fi
162}
163
164# Nested function to show stack trace
165nested_function_call() {
166 local depth="$1"
167
168 if [[ $depth -gt 0 ]]; then
169 nested_function_call $((depth - 1))
170 else
171 # Trigger error
172 false
173 fi
174}
175
176# Demo: Basic error handling
177demo_die_and_warn() {
178 echo -e "\n${BLUE}=== Die and Warn Demo ===${NC}\n"
179
180 warn "This is a warning message - script continues"
181 info "Processing continues after warning"
182 success "This step succeeded"
183
184 echo
185 echo "Example: die() would exit the script"
186 echo " die \"Critical error occurred\" $E_GENERIC"
187 echo
188}
189
190# Demo: Try/catch pattern
191demo_try_catch() {
192 echo -e "${BLUE}=== Try/Catch Pattern Demo ===${NC}\n"
193
194 # Example 1: Successful command
195 echo "Example 1: Successful command"
196 if try ls /tmp > /dev/null 2>&1; then
197 success "Command succeeded"
198 else
199 warn "Command failed (unexpected)"
200 fi
201
202 echo
203
204 # Example 2: Failing command
205 echo "Example 2: Failing command (caught)"
206 if try ls /nonexistent/path > /dev/null 2>&1; then
207 success "Command succeeded (unexpected)"
208 else
209 local error_code=$?
210 warn "Command failed with exit code $error_code (expected)"
211 info "Error was caught and handled gracefully"
212 fi
213
214 echo
215}
216
217# Demo: Try/catch with error handler
218demo_try_catch_with_handler() {
219 echo -e "${BLUE}=== Try/Catch with Error Handler ===${NC}\n"
220
221 # Define error handler function
222 handle_error() {
223 local error_code="$1"
224 echo -e "${RED}Caught error with code: $error_code${NC}"
225 echo "Performing recovery actions..."
226 echo -e "${GREEN}Recovery complete${NC}"
227 }
228
229 # Try a command that will fail
230 echo "Attempting risky operation..."
231 try some_nonexistent_command 2>/dev/null || catch handle_error
232
233 echo "Script continues after error handling"
234 echo
235}
236
237# Demo: Error codes
238demo_error_codes() {
239 echo -e "${BLUE}=== Error Code Demo ===${NC}\n"
240
241 echo "Defined error codes:"
242 echo " E_SUCCESS=$E_SUCCESS (Success)"
243 echo " E_GENERIC=$E_GENERIC (Generic error)"
244 echo " E_INVALID_ARG=$E_INVALID_ARG (Invalid argument)"
245 echo " E_FILE_NOT_FOUND=$E_FILE_NOT_FOUND (File not found)"
246 echo " E_PERMISSION_DENIED=$E_PERMISSION_DENIED (Permission denied)"
247 echo " E_NETWORK_ERROR=$E_NETWORK_ERROR (Network error)"
248 echo " E_TIMEOUT=$E_TIMEOUT (Timeout)"
249 echo " E_DEPENDENCY_MISSING=$E_DEPENDENCY_MISSING (Missing dependency)"
250
251 echo
252 echo "Example usage:"
253 echo ' [[ ! -f "$file" ]] && die "File not found: $file" $E_FILE_NOT_FOUND'
254 echo
255}
256
257# Demo: Input validation with error codes
258demo_input_validation() {
259 echo -e "${BLUE}=== Input Validation Demo ===${NC}\n"
260
261 validate_number() {
262 local input="$1"
263 local name="${2:-value}"
264
265 if [[ ! "$input" =~ ^[0-9]+$ ]]; then
266 die "Invalid $name: must be a number" $E_INVALID_ARG
267 fi
268
269 success "Valid number: $input"
270 }
271
272 validate_file() {
273 local filepath="$1"
274
275 if [[ ! -f "$filepath" ]]; then
276 die "File not found: $filepath" $E_FILE_NOT_FOUND
277 fi
278
279 if [[ ! -r "$filepath" ]]; then
280 die "Permission denied: $filepath" $E_PERMISSION_DENIED
281 fi
282
283 success "Valid file: $filepath"
284 }
285
286 # Test validation
287 echo "Testing number validation:"
288 if try validate_number "123" "test_number"; then
289 info "Validation passed"
290 else
291 warn "Validation failed (expected)"
292 fi
293
294 echo
295 echo "Testing with invalid number:"
296 if try validate_number "abc" "test_number"; then
297 warn "Validation passed (unexpected)"
298 else
299 info "Validation failed as expected"
300 fi
301
302 echo
303}
304
305# Demo: Stack trace (disabled by default to avoid script exit)
306demo_stack_trace() {
307 echo -e "${BLUE}=== Stack Trace Demo ===${NC}\n"
308
309 echo "To see a stack trace, uncomment the following line in the script:"
310 echo " # function_that_fails 3"
311 echo
312 echo "This would call a nested function that fails,"
313 echo "triggering the error handler which prints:"
314 echo " - Error code"
315 echo " - Function name"
316 echo " - Line number"
317 echo " - Failed command"
318 echo " - Full stack trace"
319 echo
320
321 # Uncomment to actually trigger error (will exit script):
322 # function_that_fails 3
323}
324
325# ============================================================================
326# Main Execution
327# ============================================================================
328
329main() {
330 echo -e "${BLUE}╔════════════════════════════════════════════╗${NC}"
331 echo -e "${BLUE}║ Error Handling Framework Demo ║${NC}"
332 echo -e "${BLUE}╚════════════════════════════════════════════╝${NC}"
333
334 # Setup error trap (commented out to avoid script exit during demos)
335 # Uncomment to enable automatic error context on failures:
336 # setup_error_trap
337
338 demo_die_and_warn
339 demo_try_catch
340 demo_try_catch_with_handler
341 demo_error_codes
342 demo_input_validation
343 demo_stack_trace
344
345 echo -e "${GREEN}=== All Error Handling Demos Complete ===${NC}\n"
346}
347
348main "$@"