Introduction to Bash Functions
Functions in Bash are reusable blocks of code that can be defined once and called multiple times throughout a script. They are essential for organizing code, reducing duplication, and creating modular, maintainable scripts. Unlike many programming languages, Bash functions are simple but powerful, with unique characteristics like handling arguments similarly to scripts.
Key Concepts
- Reusability: Write once, use many times
- Modularity: Break complex scripts into manageable pieces
- Scope: Variables can be local or global
- Return Values: Functions return exit status, not arbitrary values
- Arguments: Accessed similarly to script arguments ($1, $2, etc.)
1. Basic Function Syntax
Function Declaration
#!/bin/bash # Method 1: Using function keyword function greet { echo "Hello, World!" } # Method 2: Using parentheses (POSIX-compliant) greet2() { echo "Hello again!" } # Method 3: One-line function hello() { echo "Hi!"; } # Calling functions greet greet2 hello # Functions must be defined before they're called # This will fail: # call_early # call_early() { # echo "This function is called before definition" # } Function with Arguments
#!/bin/bash # Function that accepts arguments greet_person() { echo "Hello, $1! How is your $2?" } # Function with multiple arguments print_info() { echo "Name: $1" echo "Age: $2" echo "City: $3" echo "All arguments: $@" echo "Number of arguments: $#" } # Calling functions with arguments greet_person "John" "day" print_info "Alice" 30 "New York" # Accessing all arguments show_args() { echo "First: $1" echo "Second: $2" echo "All: $*" echo "Count: $#" } show_args "one" "two" "three" "four" Function Return Values
#!/bin/bash # Functions return exit status (0-255) check_file() { if [ -f "$1" ]; then return 0 # Success (true) else return 1 # Failure (false) fi } # Using return status if check_file "/etc/passwd"; then echo "File exists" else echo "File not found" fi # Return values are limited to 0-255 get_number() { return 42 # Returns exit code 42 } get_number echo "Return value: $?" # 42 # To return arbitrary values, use echo get_name() { echo "John Doe" } name=$(get_name) echo "Name: $name" # Multiple return values get_user_info() { echo "John" # First value echo "30" # Second value } read name age <<< "$(get_user_info)" echo "Name: $name, Age: $age" # Or using arrays get_colors() { echo "red green blue" } colors=($(get_colors)) echo "First color: ${colors[0]}" 2. Variable Scope
Local vs Global Variables
#!/bin/bash # Global variable global_var="I'm global" demo_scope() { # Local variable local local_var="I'm local" # Modifying global global_var="Modified in function" echo "Inside function:" echo " global_var = $global_var" echo " local_var = $local_var" } echo "Before function:" echo " global_var = $global_var" demo_scope echo "After function:" echo " global_var = $global_var" echo " local_var = $local_var" # Empty (not accessible) # Local variable with same name as global name="Global" test_local() { local name="Local" echo "Inside: $name" } test_local echo "Outside: $name" # Still "Global" # Declare can also create local variables test_declare() { declare var="Declared inside" echo "Inside: $var" } test_declare echo "Outside: $var" # Empty Variable Scope Best Practices
#!/bin/bash # Avoid global variables when possible # Bad counter=0 increment_bad() { ((counter++)) } # Good increment_good() { local count=$1 echo $((count + 1)) } counter=0 counter=$(increment_good $counter) # Using local with default values configure() { local host="${1:-localhost}" local port="${2:-8080}" local debug="${3:-false}" echo "Host: $host" echo "Port: $port" echo "Debug: $debug" } configure configure "example.com" 9090 true # Read-only local variables calculate() { local -r PI=3.14159 local -r radius=$1 echo "Area: $(echo "$PI * $radius * $radius" | bc)" } calculate 5 # PI cannot be changed inside function 3. Advanced Function Features
Functions with Options
#!/bin/bash # Function that accepts options process_file() { local verbose=false local backup=false local file="" # Parse options while [[ $# -gt 0 ]]; do case $1 in -v|--verbose) verbose=true shift ;; -b|--backup) backup=true shift ;; -f|--file) file="$2" shift 2 ;; -h|--help) echo "Usage: process_file [-v] [-b] -f FILE" return 0 ;; *) echo "Unknown option: $1" return 1 ;; esac done # Validate required arguments if [ -z "$file" ]; then echo "Error: File required" return 1 fi # Process file [ "$verbose" = true ] && echo "Processing file: $file" [ "$backup" = true ] && cp "$file" "$file.bak" # Do something with file wc -l "$file" } # Usage process_file -v -b -f /etc/passwd process_file --verbose --file /etc/hosts Recursive Functions
#!/bin/bash # Factorial using recursion factorial() { local n=$1 if [ $n -le 1 ]; then echo 1 else local prev=$(factorial $((n - 1))) echo $((n * prev)) fi } echo "Factorial of 5: $(factorial 5)" # Fibonacci sequence fibonacci() { local n=$1 if [ $n -le 1 ]; then echo $n else local prev1=$(fibonacci $((n - 1))) local prev2=$(fibonacci $((n - 2))) echo $((prev1 + prev2)) fi } for i in {0..10}; do echo "fib($i) = $(fibonacci $i)" done # Directory traversal traverse() { local dir="$1" local indent="$2" echo "${indent}📁 $dir" for item in "$dir"/*; do if [ -d "$item" ]; then traverse "$item" " $indent" elif [ -f "$item" ]; then echo " ${indent}📄 $(basename "$item")" fi done } traverse "/tmp" "" Functions with Variable Number of Arguments
#!/bin/bash # Sum all arguments sum_all() { local total=0 for num in "$@"; do ((total += num)) done echo $total } echo "Sum: $(sum_all 1 2 3 4 5)" # Join all arguments with separator join() { local separator="$1" shift local result="$1" shift for item in "$@"; do result="${result}${separator}${item}" done echo "$result" } echo "$(join , a b c d e)" echo "$(join ' | ' apple orange banana)" # Maximum of arguments max() { local max=$1 for num in "$@"; do if [ "$num" -gt "$max" ]; then max=$num fi done echo $max } echo "Max: $(max 42 17 93 56 28)" # Filter arguments filter() { local pattern="$1" shift for item in "$@"; do if [[ "$item" =~ $pattern ]]; then echo "$item" fi done } echo "Numbers: $(filter '^[0-9]+$' abc 123 def 456 ghi)" 4. Functions as Commands
Command-like Functions
#!/bin/bash # Function that behaves like a command git() { echo "Wrapped git command" if [ "$1" = "clone" ]; then echo "Cloning repository: $2" # Add logging or validation fi command git "$@" } # Override built-in command cd() { echo "Changing to directory: $1" builtin cd "$1" echo "Now in: $(pwd)" } # Function that pipes output list_files() { ls -la "$@" | grep -v "^total" | awk '{print $9, $5}' } list_files /etc # Function that accepts stdin process_stdin() { while read line; do echo "Processed: ${line^^}" done } echo -e "hello\nworld" | process_stdin # Command pipeline get_running_services() { systemctl list-units --type=service --state=running --no-legend | awk '{print $1}' } get_running_services | head -5 Function Libraries
#!/bin/bash # Source function library # Save as lib/utils.sh cat > lib/utils.sh << 'EOF' #!/bin/bash # Logging functions log_info() { echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $*" } log_error() { echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $*" >&2 } log_debug() { if [ "${DEBUG:-false}" = "true" ]; then echo "[DEBUG] $(date '+%Y-%m-%d %H:%M:%S') - $*" fi } # File operations safe_copy() { local src="$1" local dest="$2" if [ ! -f "$src" ]; then log_error "Source not found: $src" return 1 fi if [ -f "$dest" ]; then log_debug "Destination exists, creating backup" cp "$dest" "$dest.bak" fi cp "$src" "$dest" && log_info "Copied $src to $dest" } # String operations to_upper() { echo "${1^^}" } to_lower() { echo "${1,,}" } trim() { local var="$*" var="${var#"${var%%[![:space:]]*}"}" var="${var%"${var##*[![:space:]]}"}" echo "$var" } # Array operations array_join() { local sep="$1" shift local IFS="$sep" echo "$*" } array_contains() { local search="$1" shift for item in "$@"; do if [ "$item" = "$search" ]; then return 0 fi done return 1 } EOF # Use the library source lib/utils.sh log_info "Starting script" safe_copy "/etc/passwd" "/tmp/passwd.backup" echo "Uppercase: $(to_upper "hello world")" colors=("red" "green" "blue") if array_contains "green" "${colors[@]}"; then log_info "Found green in array" fi 5. Error Handling in Functions
Return Codes and Error Checking
#!/bin/bash # Function with error handling divide() { local dividend=$1 local divisor=$2 if [ "$divisor" -eq 0 ]; then echo "Error: Division by zero" >&2 return 1 fi echo $((dividend / divisor)) return 0 } # Check return code result=$(divide 10 2) if [ $? -eq 0 ]; then echo "Result: $result" fi if ! result=$(divide 10 0); then echo "Division failed" fi # Multiple error conditions validate_user() { local username="$1" # Check if empty if [ -z "$username" ]; then echo "Error: Username cannot be empty" >&2 return 1 fi # Check length if [ ${#username} -lt 3 ]; then echo "Error: Username too short" >&2 return 2 fi # Check characters if [[ ! "$username" =~ ^[a-zA-Z0-9_]+$ ]]; then echo "Error: Invalid characters" >&2 return 3 fi # Check if exists if id "$username" &>/dev/null; then echo "Error: User already exists" >&2 return 4 fi return 0 } # Handle different error codes validate_user "jo" case $? in 1) echo "Empty username" ;; 2) echo "Too short" ;; 3) echo "Invalid chars" ;; 4) echo "Already exists" ;; 0) echo "Valid username" ;; esac Trap and Cleanup
#!/bin/bash # Function with cleanup work_with_temp() { local temp_file=$(mktemp) # Set trap to clean up on exit trap 'rm -f "$temp_file"' RETURN echo "Working with temp file: $temp_file" echo "Some data" > "$temp_file" # Do some work cat "$temp_file" # File will be removed automatically when function returns } work_with_temp echo "Temp file should be gone" # Function with multiple exit points process_data() { local input="$1" local output="$2" local temp1=$(mktemp) local temp2=$(mktemp) # Cleanup on any exit trap 'rm -f "$temp1" "$temp2"' RETURN # Step 1 if ! grep "pattern" "$input" > "$temp1"; then echo "Pattern not found" return 1 fi # Step 2 if ! sort "$temp1" > "$temp2"; then return 2 fi # Step 3 if ! mv "$temp2" "$output"; then return 3 fi echo "Processing complete" } process_data "/etc/passwd" "/tmp/processed.txt" 6. Advanced Function Techniques
Function Composition
#!/bin/bash # Function composition compose() { local f="$1" local g="$2" local x="$3" # Apply g then f eval "$f \"\$($g \"\$x\")\"" } # Example functions double() { echo $(($1 * 2)) } square() { echo $(($1 * $1)) } add_one() { echo $(($1 + 1)) } # Compose functions result=$(compose "double" "square" 5) echo "square then double: $result" # 5^2 = 25, *2 = 50 result=$(compose "add_one" "double" 5) echo "double then add_one: $result" # 5*2 = 10, +1 = 11 # Pipeline composition pipeline() { local x="$1" shift for func in "$@"; do x=$($func "$x") done echo "$x" } result=$(pipeline 5 "square" "double" "add_one") echo "Pipeline: $result" # square(5)=25, double(25)=50, add_one(50)=51 Currying and Partial Application
#!/bin/bash # Create function with preset arguments with_prefix() { local prefix="$1" shift echo "${prefix}: $*" } # Partial application greet() { local greeting="$1" # Return a function that uses the greeting cat <<EOF greet_with() { echo "$greeting, \$1" } EOF } # Create specific greeting functions eval "$(greet "Hello")" eval "$(greet "Goodbye")" greet_with "John" # Hello, John goodbye_with "Mary" # Goodbye, Mary # Configuration presets create_server() { local default_host="$1" local default_port="$2" cat <<EOF server() { local host="\${1:-$default_host}" local port="\${2:-$default_port}" echo "Connecting to \$host:\$port" } EOF } eval "$(create_server "localhost" 8080)" server # localhost:8080 server "example.com" 9090 # example.com:9090 Memoization (Caching)
#!/bin/bash # Simple memoization declare -A CACHE memoize() { local func="$1" local arg="$2" local key="${func}_${arg}" if [ -n "${CACHE[$key]}" ]; then echo "Using cached value for $arg" echo "${CACHE[$key]}" return fi # Compute value local result=$($func "$arg") CACHE[$key]="$result" echo "$result" } # Expensive function fib() { local n=$1 if [ $n -le 1 ]; then echo $n else local prev1=$(fib $((n - 1))) local prev2=$(fib $((n - 2))) echo $((prev1 + prev2)) fi } # Memoized version memo_fib() { memoize "fib" "$1" } # Compare performance time fib 30 time memo_fib 30 time memo_fib 30 # Much faster, cached 7. Practical Function Examples
File Processing Functions
#!/bin/bash # Count lines in file count_lines() { local file="$1" if [ ! -f "$file" ]; then echo "Error: File not found" >&2 return 1 fi wc -l < "$file" } # Find and replace in file find_replace() { local file="$1" local pattern="$2" local replacement="$3" local backup="${4:-false}" if [ ! -f "$file" ]; then echo "Error: File not found" >&2 return 1 fi if [ "$backup" = "true" ]; then cp "$file" "$file.bak" fi sed -i "s/$pattern/$replacement/g" "$file" echo "Replaced all occurrences in $file" } # Extract unique words unique_words() { local file="$1" tr -cs '[:alpha:]' '\n' < "$file" | \ tr '[:upper:]' '[:lower:]' | \ sort | uniq | grep -v '^$' } # File statistics file_stats() { local file="$1" if [ ! -f "$file" ]; then echo "Error: File not found" >&2 return 1 fi echo "Statistics for: $file" echo "Size: $(du -h "$file" | cut -f1)" echo "Lines: $(wc -l < "$file")" echo "Words: $(wc -w < "$file")" echo "Characters: $(wc -c < "$file")" echo "Owner: $(stat -c %U "$file")" echo "Permissions: $(stat -c %a "$file")" } String Manipulation Functions
#!/bin/bash # Reverse a string str_reverse() { local str="$1" local reversed="" for (( i=${#str}-1; i>=0; i-- )); do reversed="${reversed}${str:$i:1}" done echo "$reversed" } # Check if palindrome is_palindrome() { local str="${1,,}" # lowercase str=$(echo "$str" | tr -d '[:punct:][:space:]') [ "$str" = "$(str_reverse "$str")" ] } # Slugify string slugify() { local str="$1" # Convert to lowercase str="${str,,}" # Replace spaces with hyphens str="${str// /-}" # Remove non-alphanumeric characters str=$(echo "$str" | sed 's/[^a-z0-9-]//g') # Remove duplicate hyphens while [[ "$str" == *--* ]]; do str="${str//--/-}" done # Trim hyphens from ends str="${str#-}" str="${str%-}" echo "$str" } # Truncate string str_truncate() { local str="$1" local max="$2" local suffix="${3:-...}" if [ ${#str} -le $max ]; then echo "$str" else echo "${str:0:$((max - ${#suffix}))}${suffix}" fi } # Test str="Hello, World!" echo "Reverse: $(str_reverse "$str")" is_palindrome "racecar" && echo "Is palindrome" echo "Slug: $(slugify "Hello World! How are you?")" echo "Truncated: $(str_truncate "$str" 10)" Math Functions
#!/bin/bash # Basic math functions abs() { echo "${1#-}" } min() { local min=$1 for num in "$@"; do [ "$num" -lt "$min" ] && min=$num done echo $min } max() { local max=$1 for num in "$@"; do [ "$num" -gt "$max" ] && max=$num done echo $max } # Average avg() { local sum=0 for num in "$@"; do ((sum += num)) done echo "scale=2; $sum / $#" | bc } # Factorial fact() { local n=$1 local result=1 for (( i=2; i<=n; i++ )); do ((result *= i)) done echo $result } # Prime check is_prime() { local n=$1 if [ "$n" -lt 2 ]; then return 1 fi local max=$(echo "sqrt($n)" | bc) for (( i=2; i<=max; i++ )); do if [ $((n % i)) -eq 0 ]; then return 1 fi done return 0 } # GCD gcd() { local a=$1 local b=$2 while [ "$b" -ne 0 ]; do local temp=$b b=$((a % b)) a=$temp done echo $a } # LCM lcm() { local a=$1 local b=$2 local g=$(gcd $a $b) echo $((a * b / g)) } # Test echo "min 5 2 8 1: $(min 5 2 8 1)" echo "max 5 2 8 1: $(max 5 2 8 1)" echo "avg 5 2 8 1: $(avg 5 2 8 1)" echo "fact 5: $(fact 5)" is_prime 17 && echo "17 is prime" echo "gcd 48 18: $(gcd 48 18)" echo "lcm 12 18: $(lcm 12 18)" System Information Functions
#!/bin/bash # Get system information get_os() { case "$(uname -s)" in Linux*) echo "Linux" ;; Darwin*) echo "macOS" ;; CYGWIN*) echo "Cygwin" ;; MINGW*) echo "MinGW" ;; *) echo "Unknown" ;; esac } get_cpu_count() { nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo "Unknown" } get_memory_info() { if [ "$(get_os)" = "Linux" ]; then free -h | awk '/^Mem:/ {print "Total: " $2, "Available: " $7}' elif [ "$(get_os)" = "macOS" ]; then vm_stat | perl -ne '/page size of (\d+)/ and $size=$1; /Pages free: (\d+)/ and printf("Free: %.2f GB\n", $1 * $size / 1073741824);' fi } get_disk_usage() { local mount_point="${1:-/}" df -h "$mount_point" | awk 'NR==2 {print "Used: " $3, "Available: " $4, "Usage: " $5}' } get_load_average() { uptime | awk -F'load average:' '{print $2}' } get_process_count() { ps aux | wc -l } # System report system_report() { echo "=== System Report ===" echo "OS: $(get_os)" echo "Hostname: $(hostname)" echo "Kernel: $(uname -r)" echo "CPU Cores: $(get_cpu_count)" echo "Load Average:$(get_load_average)" echo "Memory: $(get_memory_info)" echo "Disk: $(get_disk_usage /)" echo "Processes: $(get_process_count)" echo "Uptime: $(uptime | awk '{print $3,$4}' | sed 's/,//')" } 8. Testing and Debugging Functions
Unit Testing Functions
#!/bin/bash # Simple testing framework test_framework() { local tests_run=0 local tests_passed=0 local tests_failed=0 # Assert functions assert_equals() { local expected="$1" local actual="$2" local message="${3:-}" ((tests_run++)) if [ "$expected" = "$actual" ]; then echo "✓ PASS: $message" ((tests_passed++)) else echo "✗ FAIL: $message" echo " Expected: '$expected'" echo " Actual: '$actual'" ((tests_failed++)) fi } assert_true() { assert_equals 0 $? "$1" } assert_false() { assert_equals 1 $? "$1" } # Run test suite echo "Running tests..." echo "=================" # Define tests test_string_functions() { local result result=$(to_upper "hello") assert_equals "HELLO" "$result" "to_upper converts to uppercase" result=$(to_lower "WORLD") assert_equals "world" "$result" "to_lower converts to lowercase" result=$(trim " spaced ") assert_equals "spaced" "$result" "trim removes whitespace" } test_math_functions() { local result result=$(abs -5) assert_equals "5" "$result" "abs of -5 is 5" result=$(abs 5) assert_equals "5" "$result" "abs of 5 is 5" result=$(min 5 2 8 1) assert_equals "1" "$result" "min of [5,2,8,1] is 1" result=$(max 5 2 8 1) assert_equals "8" "$result" "max of [5,2,8,1] is 8" } test_file_functions() { local test_file="/tmp/test_$$.txt" echo "test content" > "$test_file" assert_true "check_file exists" check_file "$test_file" rm "$test_file" assert_false "check_file fails for missing file" check_file "$test_file" } # Run tests test_string_functions test_math_functions test_file_functions # Summary echo "=================" echo "Tests: $tests_run, Passed: $tests_passed, Failed: $tests_failed" return $tests_failed } # Run the tests test_framework Debugging Functions
#!/bin/bash # Debug mode flag DEBUG=${DEBUG:-false} # Debug logging debug_log() { if [ "$DEBUG" = "true" ]; then echo "[DEBUG] $(date '+%H:%M:%S') - $*" >&2 fi } # Function call tracer trace_call() { local func="$1" shift debug_log "Calling $func with args: $*" # Time the function local start=$(date +%s.%N) "$func" "$@" local result=$? local end=$(date +%s.%N) local duration=$(echo "$end - $start" | bc) debug_log "$func returned $result in ${duration}s" return $result } # Function profiler profile_function() { local func="$1" local iterations="${2:-100}" shift 2 debug_log "Profiling $func with $iterations iterations" local start=$SECONDS for ((i=0; i<iterations; i++)); do "$func" "$@" > /dev/null done local duration=$((SECONDS - start)) echo "$func: ${duration}s for $iterations iterations ($(bc <<< "scale=3; $duration / $iterations")s per call)" } # Example usage slow_function() { sleep 0.01 echo "Done" } # Enable debug DEBUG=true # Call with tracing trace_call slow_function # Profile profile_function slow_function 10 9. Function Libraries and Modules
Creating Function Libraries
#!/bin/bash # lib/strings.sh cat > lib/strings.sh << 'EOF' #!/bin/bash # String manipulation library # Check if string contains substring contains() { local haystack="$1" local needle="$2" [[ "$haystack" == *"$needle"* ]] } # Get string before delimiter before() { local str="$1" local delim="$2" echo "${str%%$delim*}" } # Get string after delimiter after() { local str="$1" local delim="$2" echo "${str#*$delim}" } # Count occurrences count_occurrences() { local str="$1" local substr="$2" local count=0 while [[ "$str" == *"$substr"* ]]; do ((count++)) str="${str#*$substr}" done echo "$count" } # Export functions EOF # lib/math.sh cat > lib/math.sh << 'EOF' #!/bin/bash # Math library # Check if number is even is_even() { [ $(( $1 % 2 )) -eq 0 ] } # Check if number is odd is_odd() { [ $(( $1 % 2 )) -ne 0 ] } # Power function pow() { local base="$1" local exp="$2" local result=1 for ((i=0; i<exp; i++)); do ((result *= base)) done echo "$result" } # Clamp value between min and max clamp() { local value="$1" local min="$2" local max="$3" if [ "$value" -lt "$min" ]; then echo "$min" elif [ "$value" -gt "$max" ]; then echo "$max" else echo "$value" fi } EOF # lib/file.sh cat > lib/file.sh << 'EOF' #!/bin/bash # File operations library # Check if file is readable is_readable() { [ -r "$1" ] } # Check if file is writable is_writable() { [ -w "$1" ] } # Get file extension get_extension() { local file="$1" echo "${file##*.}" } # Get filename without extension get_basename() { local file="$1" echo "${file%.*}" } # Safe delete (move to trash) safe_delete() { local trash="/tmp/trash" mkdir -p "$trash" mv "$1" "$trash/$(basename "$1").$(date +%s)" echo "Moved $1 to trash" } EOF # Main script using libraries source lib/strings.sh source lib/math.sh source lib/file.sh # Use library functions echo "Contains: $(contains "hello world" "world" && echo yes || echo no)" echo "Before comma: $(before "apple,banana,cherry" ",")" echo "After colon: $(after "key:value" ":")" echo "Count 'a' in 'banana': $(count_occurrences "banana" "a")" echo "Is 42 even? $(is_even 42 && echo yes || echo no)" echo "2^8 = $(pow 2 8)" echo "Clamp 15 between 10 and 20: $(clamp 15 10 20)" is_readable "/etc/passwd" && echo "/etc/passwd is readable" echo "Extension of file.txt: $(get_extension "file.txt")" Module System
#!/bin/bash # Simple module system MODULE_PATH="${MODULE_PATH:-./lib}" declare -A MODULES_LOADED # Module loader import() { local module="$1" # Check if already loaded if [ "${MODULES_LOADED[$module]}" = "1" ]; then return 0 fi # Try to find module local module_file="$MODULE_PATH/${module}.sh" if [ ! -f "$module_file" ]; then echo "Error: Module not found: $module" >&2 return 1 fi # Load module source "$module_file" MODULES_LOADED[$module]=1 # Call module init function if exists if declare -f "${module}_init" > /dev/null; then "${module}_init" fi return 0 } # List loaded modules loaded_modules() { echo "Loaded modules:" for module in "${!MODULES_LOADED[@]}"; do echo " - $module" done } # Module configuration configure_module() { local module="$1" local key="$2" local value="$3" eval "${module}_CONFIG_${key}='$value'" } # Usage import "strings" import "math" import "file" loaded_modules # Configure module configure_module "math" "precision" "4" # Use module functions strings_contains "hello" "he" && echo "Found" 10. Performance Optimization
Function Inlining and Optimization
#!/bin/bash # Function call overhead demonstration empty_function() { : } # Benchmark function calls benchmark_calls() { local iterations="${1:-10000}" echo "Benchmarking with $iterations iterations" # Direct code start=$SECONDS for ((i=0; i<iterations; i++)); do : done direct_time=$((SECONDS - start)) # Function call start=$SECONDS for ((i=0; i<iterations; i++)); do empty_function done function_time=$((SECONDS - start)) echo "Direct code: $direct_time s" echo "Function calls: $function_time s" echo "Overhead: $((function_time - direct_time)) s" } # Inline critical code # Bad is_positive() { [ "$1" -gt 0 ] } process_numbers_slow() { for num in "$@"; do if is_positive "$num"; then echo "$num is positive" fi done } # Good - inline for performance process_numbers_fast() { for num in "$@"; do if [ "$num" -gt 0 ]; then echo "$num is positive" fi done } # Cache expensive computations declare -A CACHE fibonacci_cached() { local n=$1 if [ -n "${CACHE[$n]}" ]; then echo "${CACHE[$n]}" return fi if [ $n -le 1 ]; then result=$n else local prev1=$(fibonacci_cached $((n - 1))) local prev2=$(fibonacci_cached $((n - 2))) result=$((prev1 + prev2)) fi CACHE[$n]=$result echo $result } Reducing Function Overhead
#!/bin/bash # Avoid unnecessary subshells # Bad - creates subshell get_value_bad() { echo "value" } result=$(get_value_bad) # Good - sets variable directly get_value_good() { result="value" } # Bad - multiple commands in subshell process_data_bad() { grep "pattern" "$1" | sort | uniq } # Good - process in current shell process_data_good() { grep "pattern" "$1" | sort | uniq } # Still creates pipeline subshells, but better # Use local variables for speed slow_function() { counter=0 for i in {1..1000}; do ((counter++)) done echo $counter } fast_function() { local counter=0 for i in {1..1000}; do ((counter++)) done echo $counter } # Avoid function calls in loops # Bad square() { echo $(($1 * $1)); } process_array_bad() { for num in "${@}"; do square "$num" done } # Good process_array_good() { for num in "${@}"; do echo $((num * num)) done } 11. Best Practices and Patterns
Function Documentation
#!/bin/bash # Document functions properly #=============================================================================== # @function: calculate_discount # @description: Calculate discounted price based on customer type # @param1: Original price (float) # @param2: Customer type (regular|premium|vip) # @returns: Discounted price (float) # @example: calculate_discount 100.00 "premium" #=============================================================================== calculate_discount() { local price="$1" local customer_type="${2:-regular}" case "$customer_type" in regular) echo "scale=2; $price * 0.95" | bc ;; premium) echo "scale=2; $price * 0.90" | bc ;; vip) echo "scale=2; $price * 0.85" | bc ;; *) echo "$price" ;; esac } #=============================================================================== # @function: validate_email # @description: Validate email address format # @param1: Email address to validate # @returns: 0 (success) if valid, 1 (failure) if invalid # @example: if validate_email "[email protected]"; then ... #=============================================================================== validate_email() { local email="$1" # Basic email validation regex if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then return 0 else return 1 fi } # Generate function documentation function_docs() { local func="$1" # Extract comment block before function sed -n "/^#=/,/^${func}/p" "$0" | grep -v "^${func}" } function_docs "calculate_discount" Design Patterns
#!/bin/bash # Factory pattern create_logger() { local type="$1" case "$type" in file) cat << 'EOF' logger() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "/var/log/app.log" } EOF ;; console) cat << 'EOF' logger() { echo "[$(date '+%H:%M:%S')] $*" } EOF ;; syslog) cat << 'EOF' logger() { logger -t "APP" "$*" } EOF ;; esac } # Create and use logger eval "$(create_logger "console")" logger "Application started" # Strategy pattern set_sort_strategy() { local strategy="$1" case "$strategy" in asc) sort_function() { sort; } ;; desc) sort_function() { sort -r; } ;; numeric) sort_function() { sort -n; } ;; random) sort_function() { shuf; } ;; esac } # Use strategy set_sort_strategy "desc" echo -e "3\n1\n4\n2" | sort_function # Singleton pattern get_config() { # Only load config once if [ -z "$CONFIG_LOADED" ]; then echo "Loading configuration..." declare -g -A CONFIG=( ["host"]="localhost" ["port"]="8080" ["debug"]="true" ) declare -g CONFIG_LOADED=true fi echo "${CONFIG[$1]}" } echo "Host: $(get_config "host")" echo "Port: $(get_config "port")" echo "Debug: $(get_config "debug")" # Observer pattern declare -a OBSERVERS subscribe() { OBSERVERS+=("$1") } notify() { local event="$1" shift for observer in "${OBSERVERS[@]}"; do $observer "$event" "$@" done } # Example observers email_observer() { echo "Email: $*" } log_observer() { echo "Log: $(date) - $*" } # Subscribe and notify subscribe "email_observer" subscribe "log_observer" notify "user_login" "john" "success" Error Handling Patterns
#!/bin/bash # Try-catch pattern try() { local temp_file=$(mktemp) # Redirect errors to temp file ( set -e # Exit on error "$@" 2>&1 || echo "ERROR:$?" > "$temp_file" ) if [ -f "$temp_file" ] && grep -q "^ERROR:" "$temp_file"; then local code=$(sed 's/^ERROR://' "$temp_file") rm "$temp_file" return "$code" fi rm "$temp_file" return 0 } catch() { local error_handler="$1" local error_code=$? if [ $error_code -ne 0 ]; then $error_handler "$error_code" fi } # Usage error_handler() { echo "Error occurred with code: $1" } try divide 10 0 catch error_handler # Guard clause pattern process_user() { local username="$1" local password="$2" # Guard clauses [ -n "$username" ] || { echo "Username required" >&2; return 1; } [ -n "$password" ] || { echo "Password required" >&2; return 1; } [ ${#password} -ge 8 ] || { echo "Password too short" >&2; return 1; } # Main logic echo "Processing user: $username" return 0 } # Retry pattern with_retry() { local max_attempts="${1:-3}" local delay="${2:-1}" shift 2 local attempt=1 while [ $attempt -le $max_attempts ]; do if "$@"; then return 0 fi echo "Attempt $attempt failed, retrying in ${delay}s..." >&2 sleep $delay attempt=$((attempt + 1)) delay=$((delay * 2)) # Exponential backoff done echo "All $max_attempts attempts failed" >&2 return 1 } # Usage with_retry 3 1 curl -f http://example.com/api 12. Command Summary and Cheat Sheet
Quick Reference
# Function declaration function name { commands; } name() { commands; } # Function arguments $1, $2, ... # Individual arguments $@ # All arguments as array $* # All arguments as string $# # Number of arguments # Return values return n # Return exit code (0-255) echo # Return data (capture with $(function)) # Variable scope local var # Local variable global var # Global variable (default) readonly var # Read-only variable # Function information declare -f function # Show function definition declare -F function # List function names type function # Show function type Common Patterns
| Pattern | Code |
|---|---|
| Simple function | greet() { echo "Hello"; } |
| With parameters | greet() { echo "Hi $1"; } |
| Return value | get_name() { echo "John"; } |
| Return code | check() { [ -f "$1" ]; } |
| Default values | func() { local val="${1:-default}"; } |
| Local variables | func() { local temp="$1"; } |
| Parse options | while getopts "ab:" opt; do |
Best Practices Summary
- Always use
localfor function variables - Document functions with comments
- Return meaningful exit codes
- Use
echoto return data - Keep functions focused (single responsibility)
- Validate input parameters
- Handle errors gracefully
- Use libraries for reusable functions
- Avoid side effects when possible
- Test functions thoroughly
Conclusion
Bash functions are powerful tools for creating modular, reusable, and maintainable scripts:
Key Takeaways
- Modularity: Break complex scripts into manageable pieces
- Reusability: Write once, use many times
- Scope Control: Local variables prevent naming conflicts
- Return Values: Use echo for data, return for status
- Arguments: Access via $1, $2, etc., similar to scripts
- Libraries: Create function libraries for common tasks
- Error Handling: Implement robust error checking
- Performance: Be mindful of function call overhead
When to Use Functions
- Repeated code that appears multiple times
- Complex operations that deserve a name
- Logical units that do one thing well
- API boundaries between script components
- Library code shared across scripts
Functions are fundamental to writing professional, maintainable Bash scripts. Mastering them will significantly improve your scripting capabilities.
Complete Bash Exercises with Solutions
Practice Bash scripting with solved exercises to improve your command-line and scripting skills.
Link: https://macronepal.com/complete-bash-exercises-with-solutions/
Complete Guide to Bash Crontab Command
Learn how to schedule and automate tasks efficiently using the Bash crontab command.
Link: https://macronepal.com/complete-guide-to-bash-crontab-command-schedule-tasks/
Complete Guide to Bash Arrays
Understand how to create, manage, and use arrays in Bash scripting.
Link: https://macronepal.com/complete-guide-to-bash-arrays/
Bash Quiz: Test Your Knowledge
Test your Bash scripting knowledge with quizzes and practical questions.
Link: https://macronepal.com/bash-quiz-test-your-knowledge/
Complete Guide to Bash Functions
Learn how to write reusable Bash functions for cleaner and efficient scripts.
Link: https://macronepal.com/complete-guide-to-bash-functions/
Complete Guide to Bash Loops
Master loops in Bash for repetitive tasks and automation.
Link: https://macronepal.com/complete-guide-to-bash-loops/
Complete Guide to Bash If-Else Statements
Learn decision-making in Bash using conditional statements.
Link: https://macronepal.com/complete-guide-to-bash-ifelse-statements/
Complete Guide to Bash Operators
Explore arithmetic, logical, and comparison operators in Bash.
Link: https://macronepal.com/complete-guide-to-bash-operators/
Complete Guide to Bash Data Types
Understand variables and different data types used in Bash scripting.
Link: https://macronepal.com/complete-guide-to-bash-data-types/
Complete Guide to Bash Variables
Learn how to declare, assign, and use variables in Bash scripts.
Link: https://macronepal.com/complete-guide-to-bash-variables/
Complete Guide to Bash Scripting
A complete introduction to writing Bash scripts for automation.
Link: https://macronepal.com/complete-guide-to-bash-scripting/
Complete Guide to Basic Bash Syntax
Learn the fundamental syntax needed to write Bash commands and scripts.
Link: https://macronepal.com/complete-guide-to-basic-bash-syntax/
Complete Guide to Bash Chgrp Command
Learn how to change group ownership of files and directories.
Link: https://macronepal.com/complete-guide-to-bash-chgrp-command-change-group-ownership-2/
Complete Guide to Bash Chgrp Command (Alternate)
Another detailed explanation of Bash group ownership management.
Link: https://macronepal.com/complete-guide-to-bash-chgrp-command-change-group-ownership/
Complete Guide to Bash File Permissions and Ownership
Understand Linux file permissions and ownership management.
Link: https://macronepal.com/complete-guide-to-bash-file-permissions-and-ownership/
Complete Guide to Bash Chmod Command
Learn how to modify file permissions using chmod.
Link: https://macronepal.com/complete-guide-to-bash-chmod-command-change-file-permissions/
Complete Guide to Bash Chown Command
Change file ownership using the Bash chown command.
Link: https://macronepal.com/complete-guide-to-bash-chown-command-change-file-ownership/
Complete Guide to Bash SCP Command
Securely transfer files between systems using SCP.
Link: https://macronepal.com/complete-guide-to-bash-scp-command-secure-copy/
Complete Guide to Bash SSH Command
Learn secure remote access and server management using SSH.
Link: https://macronepal.com/complete-guide-to-bash-ssh-command/
Bash Wget Command Complete Guide
Download files from the internet using the wget command.
Link: https://macronepal.com/bash-wget-command-complete-guide-to-non-interactive-network-downloader/
