Copyright (c) 2026 MindMesh Academy. All rights reserved. This content is proprietary and may not be reproduced or distributed without permission.

5.1.1. Script Structure, Variables, and Control Flow

💡 First Principle: Every bash script should start with a shebang (#!/bin/bash) and set -euo pipefail. The shebang tells the OS which interpreter to use. set -e exits on any error, -u treats unset variables as errors, -o pipefail catches errors in pipes. These three settings catch most silent failure modes that make scripts unreliable.

Script Basics:
#!/bin/bash
# Description: Example script structure
set -euo pipefail               # Exit on error, unset vars, pipe failures

# Variables — no spaces around =
NAME="Alice"
COUNT=5
CURRENT_DATE=$(date +%Y-%m-%d)  # Command substitution
DISK_USAGE=$(df -h / | tail -1 | awk '{print $5}')

# Read-only variables
readonly CONFIG_DIR="/etc/myapp"

# Arrays
FILES=("file1.txt" "file2.txt" "file3.txt")
echo "${FILES[0]}"              # First element
echo "${FILES[@]}"              # All elements
echo "${#FILES[@]}"             # Array length

# Special variables
echo "$0"   # Script name
echo "$1"   # First argument
echo "$@"   # All arguments (individually quoted)
echo "$*"   # All arguments (as single string)
echo "$#"   # Number of arguments
echo "$?"   # Exit code of last command
echo "$"   # Current process PID
echo "$!"   # PID of last background process
Conditionals:
# if/elif/else
if [ "$USER" = "root" ]; then
    echo "Running as root"
elif [ "$USER" = "alice" ]; then
    echo "Running as alice"
else
    echo "Running as $USER"
fi

# Test operators — [ ] vs [[ ]]
# [ ] is POSIX sh; [[ ]] is bash-specific but more powerful
# Always use [[ ]] in bash scripts

# String tests
[[ -z "$VAR" ]]          # True if empty
[[ -n "$VAR" ]]          # True if non-empty
[[ "$A" = "$B" ]]        # String equality
[[ "$A" != "$B" ]]       # String inequality
[[ "$A" =~ ^[0-9]+$ ]]  # Regex match (bash only)

# Numeric tests
[[ $A -eq $B ]]          # Equal
[[ $A -ne $B ]]          # Not equal
[[ $A -lt $B ]]          # Less than
[[ $A -gt $B ]]          # Greater than
[[ $A -le $B ]]          # Less than or equal
[[ $A -ge $B ]]          # Greater than or equal

# File tests
[[ -f "$FILE" ]]         # Regular file exists
[[ -d "$DIR" ]]          # Directory exists
[[ -e "$PATH" ]]         # Anything exists
[[ -r "$FILE" ]]         # Readable
[[ -w "$FILE" ]]         # Writable
[[ -x "$FILE" ]]         # Executable
[[ -s "$FILE" ]]         # Exists and non-empty

# Compound conditions
[[ -f "$FILE" && -r "$FILE" ]]   # AND
[[ -z "$A" || -z "$B" ]]         # OR

# case statement
case "$DISTRO" in
    ubuntu|debian)
        apt update ;;
    rhel|fedora|rocky)
        dnf update ;;
    *)
        echo "Unknown distro: $DISTRO"
        exit 1 ;;
esac
Loops:
# for loop — iterate over list
for FILE in /var/log/*.log; do
    echo "Processing: $FILE"
    gzip "$FILE"
done

# for loop — C-style
for ((i=1; i<=10; i++)); do
    echo "Iteration $i"
done

# for loop — over command output
for USER in $(awk -F: '$3 >= 1000 {print $1}' /etc/passwd); do
    echo "User: $USER"
done

# while loop
COUNT=0
while [[ $COUNT -lt 5 ]]; do
    echo "Count: $COUNT"
    ((COUNT++))
done

# while read — process file line by line (best practice)
while IFS= read -r LINE; do
    echo "Line: $LINE"
done < /etc/hosts

# until loop — opposite of while
until systemctl is-active nginx &>/dev/null; do
    echo "Waiting for nginx..."
    sleep 2
done
echo "Nginx is up"
Functions:
# Define function
check_disk() {
    local THRESHOLD="${1:-80}"    # Local variable; default 80 if not provided
    local USAGE
    USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
    
    if [[ $USAGE -gt $THRESHOLD ]]; then
        echo "WARNING: Disk usage at ${USAGE}% (threshold: ${THRESHOLD}%)"
        return 1                  # Non-zero = failure
    fi
    echo "Disk OK: ${USAGE}%"
    return 0
}

# Call function
check_disk 90
check_disk        # Uses default (80)

# Capture return value
if ! check_disk 75; then
    send_alert "Disk critical"
fi

⚠️ Exam Trap: $@ and $* behave differently when quoted. "$@" expands each argument as a separate word (preserving spaces within arguments). "$*" joins all arguments into one word with the first IFS character as separator. Always use "$@" when passing arguments to another command to preserve word boundaries.

Python for Linux Administration

💡 First Principle: Bash is ideal for gluing commands together, but once a script needs structured data, robust error handling, or reusable logic, Python is the better tool. XK0-006 expects you to read and reason about basic Python automation, not write large programs.

Core data types:
TypePurposeExample
str / int / float / boolScalarsname = "web01", port = 443
listOrdered, mutable sequenceservers = ["web01", "web02"]
dictKey–value mapping — ideal for named config settingscfg = {"port": 443, "tls": True}
tupleOrdered, immutable sequencepoint = (10, 20)
setUnordered unique valuesseen = {"a", "b"}

A dict is the natural structure for storing configuration settings — each setting is a key mapped to its value and looked up by name (cfg["port"]), which is why it beats a list when data is accessed by label rather than position.

Running shell commands: subprocess.run() executes an external command and returns a completed-process object exposing .returncode, .stdout, and .stderr:

import subprocess
result = subprocess.run(["systemctl", "is-active", "nginx"],
                        capture_output=True, text=True)
if result.returncode == 0:
    print("nginx active")

Reading files safely: always use the with open(...) context manager — it closes the file automatically even if an error occurs:

with open("/etc/hostname") as f:
    host = f.read().strip()

Standard modules to recognize: os (environment variables and paths — os.getenv, os.path), sys (arguments, exit codes), subprocess (run commands), json / yaml (parse config — use yaml.safe_load(), never bare yaml.load(), which can execute embedded objects), requests (HTTP/REST calls — requests.get(url) returns a response with .status_code and .json()), and concurrent.futures.ThreadPoolExecutor (run one task against many servers in parallel).

Virtual environments (venv): python3 -m venv .venv creates an isolated per-project environment so pip install packages don't pollute the system Python or conflict between projects; activate with source .venv/bin/activate.

PEP 8 is Python's style guide. Rules the exam checks: snake_case for functions and variables (check_service, not CheckService), 4-space indentation, and UPPER_CASE for constants.

⚠️ Exam Trap: Use yaml.safe_load() for any untrusted input, and reach for a dict (not a list) when settings are looked up by name.

Reflection Question: Your script has set -euo pipefail. A command in the middle of the script exits with code 1. Describe what happens and why this behavior is desirable for production scripts.

Alvin Varughese
Written byAlvin Varughese
Founder18 professional certifications