You've learned to write scripts that can make decisions. Now it's time to give your scripts stamina and flexibility. A script without a loop is like a worker who can only perform a task once. A script without arguments is a tool that can't be adjusted.
By mastering loops and arguments, you will transform your simple scripts into powerful, reusable workhorses that can process mountains of data and adapt to any situation.
The for Loop: Iterating Over Files, Ranges, and Commands
Imagine you have a stack of 100 letters and you need to put a stamp on each one. You wouldn't write a separate instruction for every single letter. You'd just think, "for each letter in this stack, apply one stamp." That is precisely what a for loop does. It takes a list of items and performs an action for each one until the list is empty.
The basic structure is wonderfully simple:
for item in list_of_items; do
# do something with the item
done
Let's see this in action with three common scenarios.
1. Iterating Over Files
This is a classic use case. Let's say you want to rename every .txt file to have a .bak extension.
#!/bin/bash
for file in *.txt; do
mv -- "<span class="math-inline">file" "</span>{file%.txt}.bak"
echo "Renamed $file to ${file%.txt}.bak"
done
The *.txt wildcard automatically creates a list of all matching files in the directory, and the loop processes them one by one.
2. Iterating Over a Range of Numbers
Sometimes you just need to do something a specific number of times. You can create a numeric range with brace expansion.
#!/bin/bash
for i in {1..5}; do
echo "This is task number $i"
done
This loop will run five times, with the variable $i holding the numbers 1, 2, 3, 4, and 5 in turn.
3. Iterating Over Command Output
You can also use a loop to process the output of another command. You do this by wrapping the command in $(). Let's say you have a file hosts.txt that lists server names, and you want to check the status of each one.
#!/bin/bash
for server in $(cat hosts.txt); do
ping -c 1 "$server"
done
The for loop is your go to tool for working through any kind of list.
The while Loop: Running Commands Until a Condition is Met
While a for loop is for iterating through a known, finite list, a while loop is different. It's a vigilant guard that keeps running as long as a certain condition is true. The moment the condition becomes false, the loop stops.
The structure looks like this:
while [[ condition ]]; do
# do something
done
A very common and powerful use for a while loop is to read a file line by line. This is more robust for processing files than using cat with a for loop, especially if lines contain spaces.
#!/bin/bash
FILENAME="tasks.txt"
while read -r line; do
echo "Processing task: $line"
done < "$FILENAME"
Here, the read command tries to read a line from the file. It returns a successful exit code (0) as long as it can read a line. Once it hits the end of the file, read fails, its exit code becomes non zero, and the while condition is no longer true, ending the loop.
Processing Command Line Arguments
So far, our scripts have been self contained. To make them truly flexible, we need to pass them information when we run them. These pieces of information are called command line arguments.
Bash makes these arguments available through special variables inside your script.
$1,$2,$3, ...: These hold the first, second, third argument, and so on.$#: This holds the total count of arguments passed to the script.$@: This special variable holds all the arguments as a list of individual strings.
Let’s create a script greet.sh that accepts a few names and greets them.
#!/bin/bash
echo "You provided <span class="math-inline">\# names\."
echo "Let's greet them all\!"
for name in "</span>@"; do
echo "Hello, $name!"
done
You would run it from your terminal like this:
./greet.sh Alice Bob Charlie
The output would be:
You provided 3 names.
Let's greet them all!
Hello, Alice!
Hello, Bob!
Hello, Charlie!
Using arguments means you can have one script that does the same job on different inputs without ever changing the script's code.
Building Flexible Scripts with Flags using getopts
Positional arguments are great, but they have a weakness. The order matters, and it's not always clear what each argument is for. Professional command line tools use flags or options to solve this, like ls -l or tar -czvf. The -l and -c are flags.
Bash provides a built in tool called getopts to let you parse these options in your own scripts. It allows you to create a proper user interface for your script.
Let's build a script that can take a -u flag for a username and an -i flag for an IP address.
#!/bin/bash
# Set default values
USER="guest"
IP_ADDR=""
while getopts "u:i:" opt; do
case "$opt" in
u) USER="$OPTARG" ;;
i) IP_ADDR="$OPTARG" ;;
\?) echo "Invalid option provided" ;;
esac
done
echo "Connecting with user: $USER"
if [[ -n "$IP_ADDR" ]]; then
echo "Target IP address: $IP_ADDR"
else
echo "No IP address specified."
fi
Let's break down the getopts part.
while getopts "u:i:" opt; do: This is the loop that processes the options.- The string
"u:i:"is the definition of our valid flags. The lettersuandiare the flags themselves. A colon:after a letter means that flag requires an argument. case "$opt" in: We use a case statement to check which optiongetoptsfound.$OPTARG: Whengetoptsfinds a flag that needs an argument (like ouruandi), it places that argument's value in the$OPTARGvariable.
You can now run this script in flexible ways:
./myscript.sh -i 192.168.1.1 -u admin
./myscript.sh -u sarah
./myscript.sh -i 10.0.0.5
By adding loops and argument parsing to your toolkit, you've made a huge leap. Your scripts can now handle repetitive tasks with grace and can be configured on the fly, making them truly powerful and professional automation tools ! 🎉