1#!/usr/bin/env bash
2set -euo pipefail
3
4# Safe Operations Framework
5# Demonstrates safe file and command operations with proper error handling
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 NC='\033[0m' # No Color
17
18# ============================================================================
19# Safe Operation Functions
20# ============================================================================
21
22# Safe cd: Change directory with error checking
23safe_cd() {
24 local target_dir="$1"
25
26 if [[ ! -d "$target_dir" ]]; then
27 echo -e "${RED}✗${NC} Directory does not exist: $target_dir" >&2
28 return 1
29 fi
30
31 if ! cd "$target_dir"; then
32 echo -e "${RED}✗${NC} Failed to change directory to: $target_dir" >&2
33 return 1
34 fi
35
36 echo -e "${GREEN}✓${NC} Changed directory to: $target_dir"
37 return 0
38}
39
40# Safe rm: Remove with confirmation and safeguards
41safe_rm() {
42 local target="$1"
43 local force="${2:-false}"
44
45 # Check if target exists
46 if [[ ! -e "$target" ]]; then
47 echo -e "${YELLOW}⚠${NC} Target does not exist: $target"
48 return 1
49 fi
50
51 # Prevent dangerous operations
52 if [[ "$target" == "/" ]] || [[ "$target" == "/home" ]] || [[ "$target" == "/usr" ]]; then
53 echo -e "${RED}✗${NC} Refusing to remove protected directory: $target" >&2
54 return 1
55 fi
56
57 # Ask for confirmation unless force mode
58 if [[ "$force" != "true" ]]; then
59 local file_type="file"
60 [[ -d "$target" ]] && file_type="directory"
61
62 echo -n "Remove $file_type '$target'? [y/N] "
63 read -r response
64
65 if [[ ! "$response" =~ ^[Yy]$ ]]; then
66 echo -e "${YELLOW}⚠${NC} Operation cancelled"
67 return 1
68 fi
69 fi
70
71 # Perform removal
72 if [[ -d "$target" ]]; then
73 rm -rf "$target"
74 else
75 rm -f "$target"
76 fi
77
78 echo -e "${GREEN}✓${NC} Removed: $target"
79 return 0
80}
81
82# Safe write: Atomic write operation (write to temp, then move)
83safe_write() {
84 local target_file="$1"
85 local content="$2"
86
87 # Create temp file in same directory as target
88 local target_dir
89 target_dir=$(dirname "$target_file")
90
91 if [[ ! -d "$target_dir" ]]; then
92 echo -e "${RED}✗${NC} Target directory does not exist: $target_dir" >&2
93 return 1
94 fi
95
96 local temp_file
97 temp_file=$(mktemp "${target_dir}/.tmp.XXXXXX")
98
99 # Write to temp file
100 if ! echo "$content" > "$temp_file"; then
101 echo -e "${RED}✗${NC} Failed to write to temp file" >&2
102 rm -f "$temp_file"
103 return 1
104 fi
105
106 # Atomic move (overwrites target)
107 if ! mv "$temp_file" "$target_file"; then
108 echo -e "${RED}✗${NC} Failed to move temp file to target" >&2
109 rm -f "$temp_file"
110 return 1
111 fi
112
113 echo -e "${GREEN}✓${NC} Safely wrote to: $target_file"
114 return 0
115}
116
117# Require command: Check if required command exists
118require_cmd() {
119 local cmd="$1"
120 local install_hint="${2:-}"
121
122 if ! command -v "$cmd" &> /dev/null; then
123 echo -e "${RED}✗${NC} Required command not found: $cmd" >&2
124
125 if [[ -n "$install_hint" ]]; then
126 echo -e "${CYAN}Hint:${NC} $install_hint" >&2
127 fi
128
129 return 1
130 fi
131
132 echo -e "${GREEN}✓${NC} Command available: $cmd"
133 return 0
134}
135
136# Retry: Retry a command with exponential backoff
137retry() {
138 local max_attempts="${1:-3}"
139 local initial_delay="${2:-1}"
140 shift 2
141 local command=("$@")
142
143 local attempt=1
144 local delay=$initial_delay
145
146 while [[ $attempt -le $max_attempts ]]; do
147 echo -e "${CYAN}Attempt $attempt/$max_attempts:${NC} ${command[*]}"
148
149 if "${command[@]}"; then
150 echo -e "${GREEN}✓${NC} Command succeeded on attempt $attempt"
151 return 0
152 fi
153
154 if [[ $attempt -lt $max_attempts ]]; then
155 echo -e "${YELLOW}⚠${NC} Command failed, retrying in ${delay}s..."
156 sleep "$delay"
157
158 # Exponential backoff
159 delay=$((delay * 2))
160 fi
161
162 ((attempt++))
163 done
164
165 echo -e "${RED}✗${NC} Command failed after $max_attempts attempts" >&2
166 return 1
167}
168
169# Safe copy: Copy with verification
170safe_cp() {
171 local source="$1"
172 local dest="$2"
173
174 # Check source exists
175 if [[ ! -e "$source" ]]; then
176 echo -e "${RED}✗${NC} Source does not exist: $source" >&2
177 return 1
178 fi
179
180 # Check destination directory exists
181 local dest_dir
182 if [[ -d "$dest" ]]; then
183 dest_dir="$dest"
184 else
185 dest_dir=$(dirname "$dest")
186 fi
187
188 if [[ ! -d "$dest_dir" ]]; then
189 echo -e "${RED}✗${NC} Destination directory does not exist: $dest_dir" >&2
190 return 1
191 fi
192
193 # Perform copy
194 if ! cp -r "$source" "$dest"; then
195 echo -e "${RED}✗${NC} Failed to copy: $source → $dest" >&2
196 return 1
197 fi
198
199 echo -e "${GREEN}✓${NC} Copied: $source → $dest"
200 return 0
201}
202
203# Create directory with parents and error checking
204safe_mkdir() {
205 local dir_path="$1"
206
207 if [[ -e "$dir_path" ]]; then
208 if [[ -d "$dir_path" ]]; then
209 echo -e "${YELLOW}⚠${NC} Directory already exists: $dir_path"
210 return 0
211 else
212 echo -e "${RED}✗${NC} Path exists but is not a directory: $dir_path" >&2
213 return 1
214 fi
215 fi
216
217 if ! mkdir -p "$dir_path"; then
218 echo -e "${RED}✗${NC} Failed to create directory: $dir_path" >&2
219 return 1
220 fi
221
222 echo -e "${GREEN}✓${NC} Created directory: $dir_path"
223 return 0
224}
225
226# ============================================================================
227# Demo Functions
228# ============================================================================
229
230demo_safe_cd() {
231 echo -e "\n${BLUE}=== Safe CD Demo ===${NC}\n"
232
233 local original_dir=$PWD
234
235 # Test 1: Valid directory
236 echo "Test 1: Change to /tmp (should succeed)"
237 safe_cd /tmp
238 echo " Current directory: $PWD"
239 echo
240
241 # Test 2: Invalid directory
242 echo "Test 2: Change to /nonexistent (should fail)"
243 safe_cd /nonexistent || echo -e " ${CYAN}Handled error gracefully${NC}"
244 echo " Current directory: $PWD"
245 echo
246
247 # Restore original directory
248 cd "$original_dir"
249}
250
251demo_safe_rm() {
252 echo -e "${BLUE}=== Safe RM Demo ===${NC}\n"
253
254 local test_dir="/tmp/safe_rm_test"
255 safe_mkdir "$test_dir"
256
257 # Create test files
258 echo "Creating test files..."
259 touch "$test_dir/file1.txt"
260 touch "$test_dir/file2.txt"
261 echo
262
263 # Test 1: Remove with confirmation (force mode for demo)
264 echo "Test 1: Remove file (force mode)"
265 safe_rm "$test_dir/file1.txt" true
266 echo
267
268 # Test 2: Try to remove non-existent
269 echo "Test 2: Remove non-existent file"
270 safe_rm "$test_dir/nonexistent.txt" true || echo -e " ${CYAN}Handled error gracefully${NC}"
271 echo
272
273 # Test 3: Try to remove protected directory
274 echo "Test 3: Try to remove protected directory"
275 safe_rm "/" true || echo -e " ${CYAN}Protection worked!${NC}"
276 echo
277
278 # Cleanup
279 rm -rf "$test_dir"
280}
281
282demo_safe_write() {
283 echo -e "${BLUE}=== Safe Write Demo ===${NC}\n"
284
285 local test_file="/tmp/safe_write_test.txt"
286
287 # Test 1: Normal write
288 echo "Test 1: Write to file"
289 safe_write "$test_file" "Hello, World!"
290 echo " Content: $(cat "$test_file")"
291 echo
292
293 # Test 2: Overwrite existing file
294 echo "Test 2: Overwrite existing file"
295 safe_write "$test_file" "Updated content"
296 echo " Content: $(cat "$test_file")"
297 echo
298
299 # Test 3: Write to non-existent directory
300 echo "Test 3: Write to non-existent directory"
301 safe_write "/nonexistent/dir/file.txt" "Test" || echo -e " ${CYAN}Handled error gracefully${NC}"
302 echo
303
304 # Cleanup
305 rm -f "$test_file"
306}
307
308demo_require_cmd() {
309 echo -e "${BLUE}=== Require Command Demo ===${NC}\n"
310
311 # Test with existing commands
312 echo "Test 1: Check for 'bash'"
313 require_cmd bash
314 echo
315
316 echo "Test 2: Check for 'ls'"
317 require_cmd ls
318 echo
319
320 # Test with non-existent command
321 echo "Test 3: Check for non-existent command"
322 require_cmd nonexistent_command_xyz "Install with: apt-get install xyz" || \
323 echo -e " ${CYAN}Handled missing dependency${NC}"
324 echo
325}
326
327demo_retry() {
328 echo -e "${BLUE}=== Retry Demo ===${NC}\n"
329
330 # Create a command that fails 2 times then succeeds
331 local attempt_file="/tmp/retry_attempt.txt"
332 echo "0" > "$attempt_file"
333
334 failing_command() {
335 local attempts
336 attempts=$(cat "$attempt_file")
337 attempts=$((attempts + 1))
338 echo "$attempts" > "$attempt_file"
339
340 if [[ $attempts -lt 3 ]]; then
341 echo " Simulating failure..."
342 return 1
343 else
344 echo " Success!"
345 return 0
346 fi
347 }
348
349 echo "Test: Command that fails twice then succeeds"
350 retry 5 1 failing_command
351 echo
352
353 # Cleanup
354 rm -f "$attempt_file"
355
356 # Test command that always fails
357 echo "Test: Command that always fails"
358 retry 3 1 false || echo -e " ${CYAN}All retries exhausted${NC}"
359 echo
360}
361
362demo_safe_operations_combined() {
363 echo -e "${BLUE}=== Combined Safe Operations Demo ===${NC}\n"
364
365 local work_dir="/tmp/safe_ops_demo"
366
367 echo "Creating workspace..."
368 safe_mkdir "$work_dir"
369
370 echo "Writing configuration file..."
371 safe_write "$work_dir/config.txt" "app_name=demo\nversion=1.0"
372
373 echo "Creating subdirectory..."
374 safe_mkdir "$work_dir/data"
375
376 echo "Copying file..."
377 safe_cp "$work_dir/config.txt" "$work_dir/data/"
378
379 echo "Listing workspace:"
380 ls -R "$work_dir" | sed 's/^/ /'
381
382 echo
383 echo "Cleaning up..."
384 safe_rm "$work_dir" true
385
386 echo
387}
388
389# ============================================================================
390# Main Execution
391# ============================================================================
392
393main() {
394 echo -e "${BLUE}╔════════════════════════════════════════════╗${NC}"
395 echo -e "${BLUE}║ Safe Operations Demo ║${NC}"
396 echo -e "${BLUE}╚════════════════════════════════════════════╝${NC}"
397
398 demo_safe_cd
399 demo_safe_rm
400 demo_safe_write
401 demo_require_cmd
402 demo_retry
403 demo_safe_operations_combined
404
405 echo -e "${GREEN}=== All Safe Operations Demos Complete ===${NC}\n"
406}
407
408main "$@"