Simple Bash Scripting to Automate a Task
Security work is full of repetition: checking the same thing on ten machines, summarising a log, collecting the same information after every change. A good professional does not repeat; they write a script. A bash script is nothing more than a text file containing the commands you already know, with a few additions to make decisions and repeat steps. In this lesson you will write your first scripts and finish with a useful audit helper for your lab.
You now know how to move around Linux, handle permissions, manage services and read logs. A script chains these actions into one reusable tool. This is also the ground for the Python scripting that appears later in the track.
Your first script
A script is a plain text file. Create one with nano hello.sh:
#!/bin/bash
# My first script
echo "Hello from $(hostname)"
echo "Today is $(date +%Y-%m-%d)"
The first line, #!/bin/bash, is the shebang: it tells the system which interpreter should run the file. Lines starting with # are comments. The notation $(command) runs a command and inserts its output in the text. To run the script, give it execute permission (lesson 2!) and call it with a path:
chmod +x hello.sh
./hello.sh
The ./ means "in the current directory": for safety, Linux does not run programs from the current directory unless you say so explicitly.
Variables
A variable holds a value. You assign without spaces around the equal sign, and you read the value with a dollar sign:
name="lab-server"
count=3
echo "Checking $name ($count items)"
Always put double quotes around variables when you use them: "$name". Without quotes, a value that contains spaces or special characters can break your command. Single quotes, on the other hand, prevent any substitution and print the text literally.
Scripts can also receive arguments from the command line, available as $1, $2, and so on, and $# gives their number. Running ./greet.sh Alice makes $1 equal to Alice. You can also ask a question while the script runs with read -p "Name? " answer.
Making decisions: if
#!/bin/bash
file="/etc/passwd"
if [ -f "$file" ]; then
echo "$file exists"
else
echo "$file is missing"
fi
The square brackets run a test. Spaces inside them matter. Handy tests: -f (is a regular file), -d (is a directory), -r (is readable), -z "$x" (is empty), "$a" = "$b" (strings are equal), and, for numbers, -eq, -ne, -lt, -gt (equal, not equal, less than, greater than).
Every command also returns an exit status: 0 means success, anything else means an error. It is stored in $?, and you can test commands directly: if grep -q "root" /etc/passwd; then ..., where -q makes grep quiet and only returns a status.
Repeating: for and while
#!/bin/bash
for user in alice bob carol; do
echo "Checking account: $user"
done
for file in /var/log/*.log; do
echo "$file has $(wc -l < "$file") lines"
done
A for loop repeats a block for each item in a list, and the list can be words, file names produced by a pattern, or the output of a command. A while loop repeats as long as a condition is true, and is often used to read a file line by line:
while read -r line; do
echo "Line: $line"
done < hosts.txt
Functions and good habits
As scripts grow, group repeated steps in functions:
check_port() {
if ss -tuln | grep -q ":$1 "; then
echo "Port $1 is listening"
else
echo "Port $1 is not listening"
fi
}
check_port 22
check_port 80
Some habits will save you pain. Start your scripts with set -u, which makes the script stop if you use a variable that does not exist (a typo would otherwise silently become an empty string). Always quote your variables. Print what the script does, so you can understand its output later. Test destructive commands first by printing them with echo before really running them. And never run a script that you downloaded without reading it: a script has all the rights of the user running it.
A security-minded example: a mini audit script
Here is a script that gathers the basic information a defender wants about a Linux machine that they administer: who can log in, which ports listen, which services run, and how many failed logins are in the authentication log. It only reads information on the local machine.
#!/bin/bash
set -u
echo "=== Audit of $(hostname) on $(date +%Y-%m-%d) ==="
echo
echo "--- Accounts with a login shell ---"
grep -v -E "nologin|false" /etc/passwd | cut -d: -f1,7
echo
echo "--- Listening TCP ports ---"
ss -tln | tail -n +2 | awk '{print $4}'
echo
echo "--- Running services (count) ---"
systemctl list-units --type=service --state=running --no-legend | wc -l
echo
echo "--- Failed SSH logins in the journal ---"
if command -v journalctl > /dev/null; then
journalctl -u ssh --no-pager 2> /dev/null | grep -c "Failed password"
else
echo "journalctl not available"
fi
Read it line by line. cut -d: -f1,7 keeps fields 1 and 7 of each line (the user name and shell), using the colon as delimiter. awk '{print $4}' prints the fourth column. tail -n +2 skips the header line. command -v checks that a command exists. grep -c counts the lines that match. The service name may be sshd on some systems, and reading the journal may need sudo: adapt it to your lab. This is exactly the kind of small, readable tool that you will keep improving.
Scheduling: cron
To run a script regularly, Linux has cron. Each user can have a table of scheduled jobs, edited with crontab -e. A line has five time fields (minute, hour, day of the month, month, day of the week) followed by the command:
30 2 * * * /home/student/lab/audit.sh > /home/student/lab/audit-latest.txt
This runs the audit every day at 02:30 and saves the result. Cron is also a classic place where attackers hide persistent tasks, so defenders check the crontabs and the /etc/cron* directories during investigations.
Your turn: write your own audit script
On the Linux server of your lab, create ~/lab/audit.sh based on the example above, but add two checks of your choice: for instance, the list of files in /home that are world-writable, or the number of installed packages (dpkg -l | wc -l on Debian-based systems). Make it executable, run it, and save the output to a file with a date in its name. In your notebook, comment on anything surprising in the result.
See a solution
echo
echo "--- World-writable files in /home ---"
find /home -type f -perm -002 2> /dev/null
echo
echo "--- Installed packages ---"
dpkg -l | tail -n +6 | wc -l
Add these blocks at the end of the script. Then run chmod +x ~/lab/audit.sh and ~/lab/audit.sh > ~/lab/audit-$(date +%Y%m%d).txt. On a fresh VM, the world-writable list should be empty, and the number of packages depends on your installation. Things worth a comment: an unexpected account with a login shell, a port you cannot explain, or a world-writable file. In a real environment, each of these would trigger a question: is it supposed to be like this? A script gives you a repeatable answer that you can compare from week to week.
Scripts and authorisation
Auditing your own machines is routine administration. A script that connects to many machines, scans a network or tests accounts is a powerful instrument: run it only on systems that you own or have written permission to test, and keep it inside your lab.
Key takeaways
- A bash script is a text file of commands starting with a shebang, made executable with
chmod +x. - Use variables (quote them!),
iftests,forandwhileloops, and functions. - Use
set -u, read before running, and test destructive commands withechofirst. - Scripts automate audits;
cronschedules them, and is also a place to check for persistence. - Next chapter: you move to the Windows world, where most company networks live, with Active Directory.