Introduction

Bash is the scripting language used to interact directly with Unix‑based operating systems through the shell. It allows administrators and security practitioners to automate repetitive tasks, chain command‑line utilities, and process large volumes of data efficiently. In modern environments, Bash is not limited to Linux systems, as Windows provides compatibility through the Windows Subsystem for Linux, enabling cross‑platform usage.

In enterprise and security‑focused environments, Bash scripting becomes essential due to the scale and velocity of data handled daily. Analysts frequently rely on scripts to enumerate systems, filter logs, test connectivity, and orchestrate reconnaissance or defensive workflows. Instead of manually executing dozens of commands, a single well‑designed script can perform complex logic reliably and repeatably.

It is important to understand that a Bash script does not spawn a new process by default. Instead, it is interpreted and executed line by line by the Bash interpreter itself. This execution model influences how variables, functions, and return codes behave throughout the script.


Components

Conditionals

Conditional execution allows a Bash script to make decisions based on runtime values. Without conditionals, scripts would execute sequentially without regard for context or errors, making them unsuitable for real‑world automation. Conditions enable scripts to react dynamically to user input, command results, or system state.

In Bash, conditionals typically rely on the if, elif, and else keywords combined with test expressions. These expressions evaluate values such as strings, integers, file attributes, or command return codes. Once a condition is met, the associated block executes and the remaining branches are skipped.

Example

#!/bin/bash

if [ $# -lt 1 ]; then
    echo "No arguments provided."
    exit 1
else
    echo "Argument received: $1"
fi

This example demonstrates basic argument validation. The script checks whether at least one argument was supplied and exits gracefully if the condition is not met. This pattern is fundamental for writing safe and predictable scripts.

If‑Else‑Fi‑Elif

The elif keyword allows multiple conditional branches to be evaluated in sequence. This structure is useful when a script must handle several mutually exclusive states or input ranges. Only the first matching condition is executed, ensuring deterministic behavior.

#!/bin/bash

value=$1

if [ "$value" -gt 10 ]; then
    echo "Value is greater than 10"
elif [ "$value" -lt 10 ]; then
    echo "Value is less than 10"
else
    echo "Value equals 10 or is invalid"
fi

This structure is commonly used when validating numeric input, selecting configuration paths, or implementing thresholds in monitoring scripts.

Arguments

Bash allows up to nine positional arguments to be accessed directly without explicit declaration. These arguments are referenced using $1 through $9, while $0 refers to the script name itself. This mechanism simplifies script invocation and parameter handling.

./scan.sh target.com 80 tcp
# $0 = scan.sh
# $1 = target.com
# $2 = 80
# $3 = tcp

Arguments are parsed based on whitespace and the internal field separator. Proper quoting is essential to avoid unexpected behavior when arguments contain spaces or special characters.

Special Variables

Bash provides special variables that expose execution context and command results. These variables are frequently used for flow control, debugging, and error handling.

echo "Arguments count: $#"
echo "All arguments: $@"
echo "Script PID: $$"
echo "Last exit code: $?"

These values allow scripts to introspect their own execution and react accordingly.

Variable Declaration

Variables in Bash are dynamically typed and treated as strings by default. Assignment does not allow spaces around the equals sign, and variable expansion requires the $ prefix.

message="Execution completed successfully"
echo "$message"

Despite their simplicity, variables are powerful when combined with command substitution and arithmetic expansion.

Arrays

Arrays allow multiple values to be stored under a single variable name. They are particularly useful when iterating over lists of hosts, ports, or filenames.

domains=(example.com test.local internal.lan)

for d in "${domains[@]}"; do
    echo "Resolving $d"
done

Each array element is indexed starting at zero, and iteration preserves element boundaries when quoted correctly.

Comparators

Comparison operators define how values are evaluated within conditions. Bash differentiates between string, integer, file, and logical comparisons, each with its own syntax and semantics.

String Operators

if [ -z "$var" ]; then
    echo "Variable is empty"
fi

String comparisons are commonly used for input validation and configuration checks.

Integer Operators

if [ "$count" -ge 5 ]; then
    echo "Threshold reached"
fi

Integer operators enable numerical logic such as counters, limits, and thresholds.

File Operators

if [ -f "/etc/passwd" ]; then
    echo "File exists"
fi

File operators are essential for scripts that manage configuration files or system resources.

Logical Operators

if [ -f "$file" ] && [ -r "$file" ]; then
    echo "File is readable"
fi

Logical operators allow compound conditions, increasing expressiveness while maintaining clarity.

Arithmetic

Bash supports arithmetic expansion for integer calculations. This functionality is commonly used for counters, loop control, and simple math.

count=0
((count++))
echo "Count: $count"

Arithmetic expansion avoids external utilities and improves performance in tight loops.


Script Control

Input and Output

User interaction is often required in scripts that support multiple execution paths. Bash provides the read command to capture user input and echo to display output.

read -p "Enter a hostname: " host
echo "You entered: $host"

This mechanism allows scripts to behave interactively while still supporting automation.

Loops

Loops enable repetitive execution of code blocks. Bash supports for, while, and until loops, each suited to different scenarios.

For

for i in {1..5}; do
    echo "Iteration $i"
done

The for loop is ideal for iterating over known sequences or lists.

While

count=0
while [ $count -lt 3 ]; do
    echo "Count: $count"
    ((count++))
done

While loops execute as long as a condition remains true.

Until

count=0
until [ $count -eq 5 ]; do
    echo "Count: $count"
    ((count++))
done

Until loops invert the condition logic and run until a condition becomes true.

Branches – Switch‑Case

case $1 in
    start) echo "Starting service" ;;
    stop) echo "Stopping service" ;;
    *) echo "Unknown option" ;;
esac

Case statements provide a clean alternative to deeply nested conditionals.


Execution

Functions

Functions group reusable logic under a single name, improving readability and maintainability. They are executed in the same shell context unless otherwise specified.

greet() {
    echo "Hello, $1"
}

greet "Alice"

Functions support parameters and return status codes, enabling modular script design.

Debugging

Debugging Bash scripts involves tracing command execution and variable expansion. The -x and -v flags instruct Bash to print commands as they are executed.

bash -x script.sh
bash -x -v script.sh

These options are invaluable when diagnosing logic errors, unexpected variable values, or execution flow issues.


Script Example

The following script demonstrates argument handling, DNS resolution, network enumeration, host discovery, and user interaction. It represents a realistic Bash utility used during reconnaissance or infrastructure analysis.

#!/bin/bash

# Check for given arguments
if [ $# -eq 0 ]; then
    echo "You must specify a target domain."
    echo "Usage: $0 <domain>"
    exit 1
else
    domain=$1
fi

# Identify IP address of the specified domain
hosts=$(host "$domain" | grep "has address" | awk '{print $4}')
ipaddr=$(echo "$hosts" | tr "\n" " ")

# Identify network range
network_range() {
    for ip in $ipaddr; do
        whois "$ip" | grep -E "NetRange|CIDR" | tee -a CIDR.txt
    done
}

# Ping discovered IP addresses
ping_hosts() {
    for host in $ipaddr; do
        ping -c 2 "$host" > /dev/null 2>&1
        if [ $? -eq 0 ]; then
            echo "[+] $host is reachable"
        else
            echo "[-] $host is unreachable"
        fi
    done
}

# Menu
echo "1) Show network range"
echo "2) Ping discovered hosts"
echo "3) Run all checks"
echo "*) Exit"
read -p "Select an option: " opt

case $opt in
    1) network_range ;;
    2) ping_hosts ;;
    3) network_range && ping_hosts ;;
    *) exit 0 ;;
esac

This script combines multiple Bash concepts into a single workflow. It validates input, processes command output, stores intermediate values, and exposes functionality through a simple menu. Each section can be extended or reused independently.


Reference

Based on personal study notes and practical scripting experience.

Repository: https://github.com/lameiro0x/security-foundations-htb-notes