Bash scripting is the duct tape of the sysadmin world, and knowing how to wield it is less about arcane magic and more about understanding how to string together simple commands to automate tedious tasks.
Let’s see how a basic script comes to life. Imagine you need to check the disk space on a few servers and email yourself if any are getting full.
#!/bin/bash
# Define the servers and the threshold
SERVERS=("server1.example.com" "server2.example.com")
THRESHOLD=90 # Percentage
for server in "${SERVERS[@]}"; do
echo "Checking disk space on $server..."
# Use ssh to run the df command remotely and capture output
# -q makes ssh quiet, -o ConnectTimeout=5 sets a 5-second timeout
# grep '/$' filters for the root partition, awk gets the percentage
# The first awk column is the percentage, the second is the mount point
DISK_USAGE=$(ssh -q -o ConnectTimeout=5 "$server" "df -h / | awk 'NR==2 {print \$5}' | sed 's/%//'")
# Check if DISK_USAGE is a number and if it's greater than the threshold
if [[ "$DISK_USAGE" =~ ^[0-9]+$ ]] && [ "$DISK_USAGE" -gt "$THRESHOLD" ]; then
echo "ALERT: Disk usage on $server is at ${DISK_USAGE}%!"
# In a real scenario, you'd add an email command here
# echo "High disk usage on $server: ${DISK_USAGE}%" | mail -s "Disk Space Alert" your_email@example.com
else
echo "Disk usage on $server is OK (${DISK_USAGE}%)."
fi
done
This script does a few things. First, #!/bin/bash (the "shebang") tells the system to execute this script using Bash. Then, we define an array SERVERS holding the hostnames and an integer THRESHOLD. The for loop iterates through each server. Inside the loop, ssh connects to the remote server, executes df -h / (which shows disk usage for the root filesystem in a human-readable format), and then pipes the output through awk and sed to extract just the percentage number. The if statement compares this number to our THRESHOLD.
The real magic of scripting isn’t in complex syntax, but in how it orchestrates existing tools. You’re not reinventing df or ssh; you’re just telling them when and how to talk to each other. The $(command) syntax, called command substitution, is crucial here – it runs the command inside and replaces the $(...) with the command’s output.
When you run this script (after making it executable with chmod +x your_script.sh), you’re essentially creating a custom command. The for loop is a fundamental control structure, allowing you to repeat actions. Variables like SERVERS and THRESHOLD let you easily configure the script without digging into the logic. Error handling, like checking if DISK_USAGE is actually a number with [[ "$DISK_USAGE" =~ ^[0-9]+$ ]], prevents unexpected behavior if ssh fails or returns garbage.
Consider the awk 'NR==2 {print \$5}' part. df -h / typically outputs a header line (NR==1) and then the data for the root partition (NR==2). We want the 5th column ($5) of that second line, which is the percentage. The backslash before the dollar sign (\$5) is necessary because awk itself uses $ for variable expansion, so we need to escape it to tell Bash to pass the literal $ to awk.
The real power comes when you combine this with other commands. Imagine you want to archive old log files on each server. You could add another ssh command to run find and tar within the same loop, or even copy the logs back to a central location using scp. The script becomes a workflow orchestrator.
One thing that trips people up early is quoting. Notice the double quotes around "$server" and "${SERVERS[@]}". These are important. Double quotes prevent word splitting and globbing, ensuring that if a hostname had a space (unlikely, but possible in some contexts), it would be treated as a single argument. The "${SERVERS[@]}" syntax expands each element of the array SERVERS as a separate word, correctly handling spaces within array elements if they existed.
Once you’ve mastered basic loops, variables, and command substitution, you’ll start seeing opportunities everywhere to automate repetitive tasks, from deploying configurations to monitoring system health. The next step is understanding how to handle more complex logic with case statements and functions, and how to pass arguments into your scripts.