No history yet

Control Structures

Making Scripts Intelligent

A script that runs the same commands in the same order every time is useful, but limited. To build powerful automation for security tasks, your scripts need to make decisions. They must react to their environment, handle unexpected situations, and change their behavior based on specific conditions. This is where control structures come in. They are the logic that transforms a simple sequence of commands into an intelligent agent capable of performing complex tasks like system triage or data collection.

Conditional Logic with if

The most fundamental control structure is the if statement. It allows a script to execute a block of code only when a certain condition is true. For many security operations, the first and most important check is for privileges. A forensics script, for example, often needs root access to read protected files.

You can check if the script is being run by the root user by testing the value of a special shell variable. If the condition isn't met, the script can print an error and stop.

# Check for root privileges
if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root" 
   exit 1
fi

echo "Running with root privileges..."
# ...proceed with forensic tasks

In this example, [[ $EUID -ne 0 ]] is the test. It checks if the Effective User ID is not equal (-ne) to 0, which is the ID for the root user. If the test is true, the script prints a message and exits with a non-zero exit code, signaling that an error occurred. If the test is false (meaning the user is root), the code inside the if block is skipped, and the script continues.

You can create more complex logic using elif (else if) and else to handle multiple conditions. For instance, a script might check for a specific tool, and if it's not found, check for an alternative before giving up.

if command -v nmap &> /dev/null; then
    echo "nmap found. Starting network scan..."
    nmap -sV 192.168.1.0/24
elif command -v masscan &> /dev/null; then
    echo "nmap not found, using masscan instead."
    masscan 192.168.1.0/24 -p80,443
else
    echo "Neither nmap nor masscan are installed. Cannot scan network."
    exit 1
fi

Building Menus with case

When you have a script that can perform several distinct actions, a long chain of if-elif statements can become clumsy. The case statement is a much cleaner way to handle this. It compares a single variable against several possible values, or patterns, and executes a block of code for the first match.

This is perfect for creating scripts that act like a command-line tool, where the user provides an argument to specify what they want to do.

#!/bin/bash

ACTION=$1

case $ACTION in
   "check-processes")
      echo "Checking for suspicious processes..."
      ps aux --sort=-%mem | head -n 10
      ;;
   "collect-logs")
      echo "Collecting critical system logs..."
      tar -czf system_logs.tar.gz /var/log/syslog /var/log/auth.log
      ;;
   "scan-files")
      echo "Scanning for known malware signatures..."
      # clamscan -r /home (example)
      ;;
   *)
      echo "Usage: $0 {check-processes|collect-logs|scan-files}"
      exit 1
      ;;
esac

Here, the script checks the first command-line argument ($1). The case statement then matches it against the defined patterns. Each block is terminated with ;;. The * pattern acts as a catch-all, handling any input that doesn't match the other options, which is useful for printing a help message.

Iteration with Loops

Loops are essential for automating repetitive tasks. Instead of writing the same command over and over, you can use a loop to perform an action on a list of items, such as files, users, or IP addresses.

for loops are best when you have a known, finite list of items to iterate over. while loops are ideal for situations where the number of iterations isn't known beforehand, such as reading a file line by line.

Imagine you need to check the integrity of several critical system files. A for loop makes this simple. You can define an array of file paths and loop through it, calculating a hash for each one.

#!/bin/bash

# An array of files to monitor
CRITICAL_FILES=("/etc/passwd" "/etc/shadow" "/etc/sudoers")

# Loop through each file in the array
for file in "${CRITICAL_FILES[@]}"; do
  if [[ -f "$file" ]]; then
    # Calculate the SHA256 hash and print it
    sha256sum "$file"
  else
    echo "Warning: $file does not exist!"
  fi
done

A while loop is perfect for processing a file. For example, you could read a list of IP addresses from a file and run a command against each one. The read command pulls one line from the input at a time, and the loop continues until there are no more lines to read.

#!/bin/bash

TARGET_IPS="ips_to_scan.txt"

if [[ ! -f "$TARGET_IPS" ]]; then
  echo "Error: Input file '$TARGET_IPS' not found."
  exit 1
fi

# Read the file line by line
while IFS= read -r ip; do
  echo "Pinging $ip..."
  ping -c 1 "$ip" > /dev/null
  if [[ $? -eq 0 ]]; then
    echo "$ip is reachable."
  else
    echo "$ip is not reachable."
  fi
done < "$TARGET_IPS"

Mastering these control structures is the key to elevating your Bash scripts from simple command lists to sophisticated tools that can intelligently automate your security workflows.