Course Outline (Part 8)

In Part 8 of the Linux Bash Course, we cover the structures required for writing maintainable and complex automation tools. You will learn how to declare and manipulate indexed and associative arrays, how to package code into reusable functions with custom return mechanisms, and how to use advanced parameter expansion for fast, built-in string parsing.

For more details on variable expansion modifiers, read the GNU Bash manual on Parameter Expansion.


Chapter 22: Arrays & Dictionaries

22.1 Indexed Arrays: Declaration

Indexed arrays store lists of values referenced by integer indices. They can be created implicitly by assignment or explicitly using the declare -a command.

  • Syntax:
    # Explicit declaration
    declare -a array_name
    
    # Inline initialization
    array_name=(value1 value2 value3)
  • Example Command:
    declare -a TECH_STACK
    TECH_STACK=("Astro" "Docker" "Bash" "Azure")
    echo "Array declared successfully."
  • Expected Output:
    Array declared successfully.
  • Flag & Command Breakdown:
    • declare -a: Instructs the shell to treat the specified variable name as an indexed array.

22.2 Assigning and Accessing Array Elements

Array indices start at 0. When accessing array elements, you must wrap the variable in curly braces (e.g., ${array[index]}) to prevent the shell from interpreting the index brackets as normal string characters.

  • Syntax:
    array[index]=value
    ${array[index]}
  • Example Command:
    COURSES=("Linux" "Git" "Python")
    COURSES[1]="Git-VCS" # Reassign index 1
    echo "First element: ${COURSES[0]}"
    echo "Second element: ${COURSES[1]}"
  • Expected Output:
    First element: Linux
    Second element: Git-VCS

22.3 Getting All Elements: ${arr[@]}

To expand all elements in an array, use the index @ or *.

  • "${arr[@]}": Expands each element as a distinct, quoted string. This is the safest choice for arrays containing values with spaces.

  • "${arr[*]}": Merges all array elements into a single string separated by the first character of the IFS variable.

  • Syntax:

    "${array[@]}"
    "${array[*]}"
  • Example Command:

    SERVERS=("web-01 server" "database-01 server")
    echo "Using \${SERVERS[*]}:"
    for s in "${SERVERS[*]}"; do
        echo "Server: $s"
    done
    echo "Using \${SERVERS[@]}:"
    for s in "${SERVERS[@]}"; do
        echo "Server: $s"
    done
  • Expected Output:

    Using ${SERVERS[*]}:
    Server: web-01 server database-01 server
    Using ${SERVERS[@]}:
    Server: web-01 server
    Server: database-01 server

22.4 Getting Array Length: ${#arr[@]}

Prepending a # character inside the parameter expansion brackets returns the number of elements in the array.

  • Syntax:
    ${#array_name[@]}
  • Example Command:
    DEVICES=("PC" "Phone" "Tablet" "Router")
    echo "Total devices: ${#DEVICES[@]}"
  • Expected Output:
    Total devices: 4

22.5 Slicing Arrays

You can extract a subset of array elements using the slicing syntax, which takes a start index and an optional length parameter.

  • Syntax:
    "${array[@]:start:length}"
  • Example Command:
    NUMBERS=(zero one two three four five)
    echo "Slice (index 2, length 3): ${NUMBERS[@]:2:3}"
    echo "Slice (from index 3 onward): ${NUMBERS[@]:3}"
  • Expected Output:
    Slice (index 2, length 3): two three four
    Slice (from index 3 onward): three four five

22.6 Associative Arrays (Dictionaries)

Associative arrays map values to string keys (key-value dictionaries) instead of integer indices.

  • Important: You must explicitly declare associative arrays using the declare -A command before using them.

  • Syntax:

    declare -A dictionary_name
    dictionary_name[key]=value
  • Example Command:

    declare -A SITE_METRICS
    SITE_METRICS[domain]="freetechlearner.com"
    SITE_METRICS[uptime]="99.9%"
    SITE_METRICS[ssl]="active"
    
    echo "Domain: ${SITE_METRICS[domain]}"
    echo "SSL Status: ${SITE_METRICS[ssl]}"
  • Expected Output:

    Domain: freetechlearner.com
    SSL Status: active
  • Flag & Command Breakdown:

    • declare -A: Declares an associative (dictionary) array.

22.7 Iterating Over Arrays

To iterate over array keys instead of values, prepend an exclamation mark (!) to the array name.

  • Syntax:
    # Loop over keys
    for key in "${!array[@]}"; do ...
  • Example Command:
    declare -A USER_ROLES
    USER_ROLES[suresh]="admin"
    USER_ROLES[john]="developer"
    
    for user in "${!USER_ROLES[@]}"; do
        echo "User: $user -> Role: ${USER_ROLES[$user]}"
    done
  • Expected Output:
    User: suresh -> Role: admin
    User: john -> Role: developer

22.8 Removing Array Elements

Use the unset command to delete a specific element at an index/key or to delete an entire array.

  • Syntax:
    unset array[index]     # Removes single element
    unset array            # Removes entire array
  • Example Command:
    FRUITS=("Apple" "Banana" "Orange")
    unset FRUITS[1] # Delete Banana
    echo "Fruits array length: ${#FRUITS[@]}"
    echo "Elements matching keys: ${FRUITS[@]}"
  • Expected Output:
    Fruits array length: 2
    Elements matching keys: Apple Orange

22.9 Array from Command Output

You can use mapfile (also called readarray) to load lines of command output or files directly into an array. This is safer than parsing raw string loops.

  • Syntax:
    mapfile -t array_name < <(command)
  • Example Command:
    printf "Line_1\nLine_2\nLine_3\n" > data.txt
    declare -a FILE_LINES
    mapfile -t FILE_LINES < data.txt
    echo "First Line: ${FILE_LINES[0]}"
    echo "Second Line: ${FILE_LINES[1]}"
  • Expected Output:
    First Line: Line_1
    Second Line: Line_2
  • Flag & Command Breakdown:
    • mapfile: Reads lines from standard input into an indexed array.
    • -t: Strips the trailing newline character from each line.

22.10 Passing Arrays to Functions

Bash functions do not support receiving array variables directly. You must expand the array into a list of parameters when calling the function, and then reconstruct it inside the function body.

  • Example Script:
    #!/bin/bash
    show_list() {
        local -a func_arr=("$@") # Reconstruct array
        echo "Items received: ${#func_arr[@]}"
        for item in "${func_arr[@]}"; do
            echo " - $item"
        done
    }
    
    SYSTEMS=("Linux" "Windows" "macOS")
    show_list "${SYSTEMS[@]}"

Chapter 23: Functions in Bash

23.1 Defining a Function (Two Syntaxes)

Functions group reusable blocks of code. You can define them using either of two equivalent syntaxes:

  • Syntax:
    # Method 1 (Standard)
    function_name() {
        # commands
    }
    
    # Method 2 (Word Prefix)
    function function_name {
        # commands
    }

23.2 Calling a Function

To invoke a function, call its name like any normal shell command. Do not use parentheses () when calling a function.

  • Syntax:
    function_name
  • Example Command:
    greet_user() {
        echo "Welcome back, developer!"
    }
    greet_user
  • Expected Output:
    Welcome back, developer!

23.3 Passing Arguments to Functions

Functions capture arguments using local positional parameters ($1, $2, etc.), which are independent of the global script arguments.

  • Syntax:
    function_name arg1 arg2
  • Example Command:
    setup_env() {
        echo "Environment: $1"
        echo "Version: $2"
    }
    setup_env "Production" "v1.2.0"
  • Expected Output:
    Environment: Production
    Version: v1.2.0

23.4 Returning Values (Numeric Only)

The return statement in Bash returns an exit status (an integer from 0 to 255) to the caller, not a string or complex value.

  • Convention: 0 represents success, and non-zero values indicate errors.

  • Syntax:

    return exit_code
  • Example Command:

    check_root() {
        if [[ $EUID -eq 0 ]]; then
            return 0 # Success
        else
            return 1 # Error
        fi
    }
    check_root
    echo "Is root exit code: $?"
  • Expected Output:

    Is root exit code: 1
  • Flag & Command Breakdown:

    • $EUID: A read-only shell variable holding the effective User ID of the current user. Root users always have a UID of 0.

23.5 Capturing Function Output

To capture text output from a function, capture stdout using command substitution $().

  • Syntax:
    variable=$(function_name)
  • Example Command:
    get_server_time() {
        date +"%H:%M"
    }
    CURRENT_TIME=$(get_server_time)
    echo "Server time: $CURRENT_TIME"
  • Expected Output:
    Server time: 13:40

23.6 Local Variables in Functions

Always declare internal function variables with the local keyword. This prevents functions from accidentally overwriting variables defined in the global script scope.

  • Syntax:
    local variable_name=value

23.7 Function Libraries (Sourcing Files)

You can build libraries of reusable functions by putting them in helper files (e.g. lib.sh) and loading them using the source command.

  • Syntax:
    source /path/to/library.sh
  • Example Command:
    # Create library
    echo "sys_log() { echo \"[LOG] \$1\"; }" > lib.sh
    # Source library
    source lib.sh
    sys_log "Libraries imported successfully."
    rm -f lib.sh
  • Expected Output:
    [LOG] Libraries imported successfully.

23.8 Recursive Functions

Bash supports recursive functions—functions that call themselves.

  • Important: You must define clear exit conditions to prevent infinite loops, which will exhaust shell memory and cause script crashes.

  • Example Script:

    #!/bin/bash
    # Calculate factorial recursively
    factorial() {
        local num=$1
        if [[ $num -le 1 ]]; then
            echo 1
        else
            local prev=$((num - 1))
            local sub_factorial=$(factorial $prev)
            echo $((num * sub_factorial))
        fi
    }
    echo "Factorial of 5 is: $(factorial 5)"

23.9 Exporting Functions

You can make functions available to subshells and child processes using the -f flag with the export command.

  • Syntax:
    export -f function_name
  • Example Command:
    say_hello() {
        echo "Hello from subshell"
    }
    export -f say_hello
    bash -c "say_hello"
  • Expected Output:
    Hello from subshell
  • Flag & Command Breakdown:
    • export -f: Exports the specified function name to the environment of subsequent commands.
    • bash -c: Evaluates the following string command block in a new subshell environment.

23.10 Function Overriding and Hints

You can override standard system commands by defining a function with the same name. To run the original system command from inside an overridden function, prefix the command with the command keyword.

  • Example Command:
    # Override standard ls command
    ls() {
        echo "Listing directory securely..."
        command ls -lh "$@"
    }
    ls test_run.sh
  • Expected Output:
    Listing directory securely...
    -rwxr-xr-x 1 suresh suresh 17 Jun 12 13:35 test_run.sh

Chapter 24: String Manipulation

24.1 String Length: ${#string}

Returns the total character count of a string.

  • Syntax:
    ${#string_variable}
  • Example Command:
    TEXT="BashScript"
    echo "Length: ${#TEXT}"
  • Expected Output:
    Length: 10

24.2 Substring Extraction: ${string:start:len}

Extracts a range of characters from a string, starting at a 0-based offset. If length is omitted, it extracts to the end of the string.

  • Syntax:
    ${variable:offset:length}
  • Example Command:
    URL="freetechlearner.com"
    echo "Protocol-like prefix: ${URL:0:4}"
    echo "Suffix: ${URL:12}"
  • Expected Output:
    Protocol-like prefix: free
    Suffix: er.com

24.3 Removing Shortest Prefix: ${string#pattern}

Trims the shortest matching pattern from the beginning of a string.

  • Syntax:
    ${variable#pattern}
  • Example Command:
    PATH_VAL="/usr/local/bin"
    echo "Trimmed prefix: ${PATH_VAL#*/}"
  • Expected Output:
    Trimmed prefix: usr/local/bin

24.4 Removing Longest Prefix: ${string##pattern}

Trims the longest matching pattern (greedy match) from the beginning of a string. This is commonly used to extract filenames from absolute file paths.

  • Syntax:
    ${variable##pattern}
  • Example Command:
    FILE_PATH="/var/log/nginx/access.log"
    echo "Filename only: ${FILE_PATH##*/}"
  • Expected Output:
    Filename only: access.log

24.5 Removing Shortest Suffix: ${string%pattern}

Trims the shortest matching pattern from the end of a string. This is useful for stripping file extensions.

  • Syntax:
    ${variable%pattern}
  • Example Command:
    FILE="main.go.bak"
    echo "Trimmed extension: ${FILE%.*}"
  • Expected Output:
    Trimmed extension: main.go

24.6 Removing Longest Suffix: ${string%%pattern}

Trims the longest matching pattern (greedy match) from the end of a string.

  • Syntax:
    ${variable%%pattern}
  • Example Command:
    FILE="main.go.bak"
    echo "Base name: ${FILE%%.*}"
  • Expected Output:
    Base name: main

24.7 Search and Replace: ${string/old/new}

Replaces the first occurrence of a matching pattern in a string with a new value.

  • Syntax:
    ${variable/pattern/replacement}
  • Example Command:
    ANIMAL="black cat, white cat"
    echo "Replaced: ${ANIMAL/cat/dog}"
  • Expected Output:
    Replaced: black dog, white cat

24.8 Global Replace: ${string//old/new}

Replaces all occurrences of a matching pattern in a string.

  • Syntax:
    ${variable//pattern/replacement}
  • Example Command:
    ANIMAL="black cat, white cat"
    echo "Global replaced: ${ANIMAL//cat/dog}"
  • Expected Output:
    Global replaced: black dog, white dog

24.9 Uppercase/Lowercase: ${string^} and ${string,,}

  • ${var^}: Capitalizes the first character.

  • ${var^^}: Converts the entire string to uppercase.

  • ${var,}: Lowercases the first character.

  • ${var,,}: Converts the entire string to lowercase.

  • Example Command:

    STR="linuxBASH"
    echo "Upper: ${STR^^}"
    echo "Lower: ${STR,,}"
    echo "Capitalize First: ${STR^}"
  • Expected Output:

    Upper: LINUXBASH
    Lower: linuxbash
    Capitalize First: LinuxBASH

24.10 Parameter Expansion Default Values

You can use parameter expansion to handle unset or empty variables gracefully:

  • ${var:-default}: Returns default if variable is unset or empty, but does not modify the variable.

  • ${var:=default}: Returns default if variable is unset or empty, and assigns it to the variable.

  • ${var:?error_msg}: Prints error_msg and exits the script if the variable is unset or empty.

  • Example Command:

    unset DB_PORT
    echo "Port default: ${DB_PORT:-5432}"
    echo "Original variable state: $DB_PORT"
    echo "Port assign: ${DB_PORT:=5432}"
    echo "Variable state after assignment: $DB_PORT"
  • Expected Output:

    Port default: 5432
    Original variable state: 
    Port assign: 5432
    Variable state after assignment: 5432