Lesson: Loops, Arguments & Functions
What you'll learn
- How to repeat work with
forandwhileloops. - How to read arguments passed to your script:
$1,$2,$#, and"$@". - How to write functions to organize and reuse code, and how they return values.
- How to use basic arrays to hold lists of items.
By the end you can write scripts that process many items, accept input from the command line, and stay organized as they grow.
The lesson
So far each script ran top to bottom once. Real automation repeats — over every file, every server, every line of a log — and takes input so the same script handles different jobs. That is loops, arguments, and functions.
Run everything on the Jumpbox (10.100.100.254, user ubuntu).
1. The for loop
A for loop walks through a list, binding each item to a variable in turn:
for name in alice bob carol; do
echo "Hello, $name"
done
The flow:
list: alice bob carol
| | |
v v v
name= name= name= <- body runs once per item
The list can come from a glob (file pattern), command substitution, or a number range:
for f in /etc/*.conf; do # every .conf file
echo "Found config: $f"
done
for n in $(seq 1 5); do # 1 2 3 4 5
echo "Attempt $n"
done
for i in {1..3}; do # brace expansion: 1 2 3
echo "Round $i"
done
Always quote "$f" when you use the loop variable — filenames can contain spaces. To process the lines of a file safely, prefer a while read loop (next).
2. The while loop
A while loop repeats as long as a command keeps succeeding (exit 0):
count=1
while [[ $count -le 3 ]]; do
echo "count is $count"
count=$((count + 1)) # arithmetic with $(( ))
done
$(( ... )) does integer math. The most reliable way to read a file or command output line by line is:
while read -r line; do
echo "log line: $line"
done < /var/log/syslog
This handles spaces and odd characters far better than a for loop over file contents. You can also pipe into it: df -h | while read -r line; do ...; done.
To stop early use break; to skip to the next iteration use continue:
for f in *.txt; do
[[ -s $f ]] || continue # skip empty files
echo "processing $f"
done
3. Script arguments
When you run ./deploy.sh web01 prod, the words after the script name become positional parameters:
./deploy.sh web01 prod
$0 $1 $2
$0— the script's own name.$1,$2, ... — the first, second, ... argument.$#— the count of arguments."$@"— all arguments, each kept as a separate quoted word.
#!/usr/bin/env bash
echo "Script: $0"
echo "First arg: $1"
echo "Number of args: $#"
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <hostname> [environment]" >&2
exit 1
fi
for arg in "$@"; do
echo "Got argument: $arg"
done
Use "$@" (with quotes), not $* or unquoted $@. Quoted "$@" preserves arguments that contain spaces; the others mangle them. Checking $# up front and printing a usage line is the mark of a polished script.
4. Functions
A function is a named block of commands you can call repeatedly. It keeps scripts readable and avoids copy-paste:
#!/usr/bin/env bash
log() {
echo "[$(date +%T)] $*"
}
check_dir() {
local dir="$1" # 'local' keeps the variable inside the function
if [[ -d $dir ]]; then
log "OK: $dir exists"
else
log "MISSING: $dir"
fi
}
check_dir /etc
check_dir /nope
Key points:
- Define a function before you call it.
- Inside a function, arguments are
$1,$2,"$@"— just like a script. The function above reads its argument as$1. - Use
localfor variables so they do not leak out and clobber others. This matters as scripts grow. $*and"$@"inside a function refer to the function's own arguments.
5. Return values from functions
Functions return an exit code (0–255), not a string, via return. That code feeds straight into if:
is_running() {
systemctl is-active --quiet "$1" # exits 0 if the service is active
}
if is_running ssh; then
echo "ssh is up"
else
echo "ssh is down"
fi
To get data (a string or number) out of a function, have it echo the value and capture it with command substitution:
disk_pct() {
df --output=pcent "$1" | tail -1 | tr -dc '0-9'
}
used=$(disk_pct /)
echo "Root filesystem is ${used}% full"
So: return for success/failure, echo + $( ) for actual values. Mixing these up is a classic beginner bug.
6. Basic arrays
An array holds an ordered list of values in one variable:
services=(ssh cron systemd-journald)
echo "${services[0]}" # ssh (first element, index 0)
echo "${services[@]}" # all elements
echo "${#services[@]}" # 3 (count of elements)
services+=(nginx) # append an element
Loop over an array with quoted "${services[@]}":
for svc in "${services[@]}"; do
if systemctl is-active --quiet "$svc"; then
echo "$svc: up"
else
echo "$svc: DOWN"
fi
done
Arrays are perfect for "a list of things to check" — services, hosts, directories — which is exactly what your health-check assignment needs.
7. Putting it together
#!/usr/bin/env bash
# Check several services passed as arguments; exit non-zero if any is down.
if [[ $# -eq 0 ]]; then
echo "Usage: $0 <service> [service...]" >&2
exit 1
fi
failed=0
for svc in "$@"; do
if systemctl is-active --quiet "$svc"; then
echo "$svc: up"
else
echo "$svc: DOWN" >&2
failed=$((failed + 1))
fi
done
echo "$failed service(s) down"
[[ $failed -eq 0 ]] # last command's exit code becomes the script's
This takes a variable list of arguments, loops over them, counts failures, and sets a meaningful exit code — clean, reusable, and ready for Git.
Dig deeper
- Bash Manual: Looping Constructs
- Bash Manual: Shell Functions
- Bash Manual: Arrays
- Positional parameters and
"$@"(Red Hat sysadmin blog) - Bash functions tutorial (Linux Handbook)
Search terms
bash for loop over files examplebash while read line file safelybash positional parameters $1 $@ $#bash quote "$@" vs $* differencebash function return value vs echobash array loop ${array[@]}
Check yourself
- When looping over the lines of a file, why is
while read -r linepreferred over aforloop? - What is the difference between
$#,$1, and"$@"? - Why should you write
"$@"with quotes instead of$@or$*? - How does a function "return" a number versus an actual string value?
- Given
services=(ssh cron nginx), how do you print the number of elements?
No comments to display
No comments to display