safe_ops.sh

Download
bash 409 lines 10.6 KB
  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 "$@"