Bash scripting basics: variables, conditions, loops and exit codes
In short: Bash is the default shell on most Linux systems and the quickest way to automate small jobs. A good script starts with a shebang and safety options, quotes its variables, checks exit codes and keeps logic in small functions.
Why engineers still write Bash
Bash (the Bourne Again SHell) is the command interpreter on almost every Linux server, jump host, firewall appliance shell and network operating system that exposes a Linux prompt. If you type the same five commands every morning, a Bash script can run them for you. It needs no extra installation, starts instantly and glues together tools that already exist, such as ping, ssh, grep and curl.
Bash is not the right tool for everything. Once a job needs data structures, API calls with JSON or careful error handling, Python is usually easier to read and test. A sensible rule is: if the script grows past about 100 lines, consider rewriting it in Python.
Your first script
Save the following as check_hosts.sh, make it executable with chmod +x check_hosts.sh and run it with ./check_hosts.sh.
#!/usr/bin/env bash
set -euo pipefail
SITE="branch-01"
HOSTS=("192.168.10.1" "192.168.10.2" "192.168.10.10")
for h in "${HOSTS[@]}"; do
if ping -c 1 -W 2 "$h" > /dev/null 2>&1; then
echo "$SITE $h up"
else
echo "$SITE $h DOWN"
fi
done
- Shebang: the first line tells the system which interpreter to use.
/usr/bin/env bashfinds Bash wherever it is installed. set -euo pipefail: stop on any failing command (-e), treat unset variables as errors (-u) and make a pipeline fail if any part fails (pipefail).- Variables: assign with no spaces around
=; read with"$NAME". Arrays use"${ARRAY[@]}". - Redirection:
> /dev/null 2>&1throws away normal output and errors, because you only care about the exit code.
Conditions, exit codes and functions
Every command returns an exit code: 0 means success, anything else means failure. if simply runs a command and checks that code. For comparisons you use the test brackets [[ ... ]].
backup_dir="/var/backups/net"
log() { echo "$(date '+%F %T') $*"; }
if [[ ! -d "$backup_dir" ]]; then
log "creating $backup_dir"
mkdir -p "$backup_dir"
fi
case "${1:-status}" in
status) log "status requested" ;;
clean) find "$backup_dir" -name '*.cfg' -mtime +30 -print ;;
*) echo "usage: $0 [status|clean]"; exit 2 ;;
esac
$1 is the first argument, and ${1:-status} supplies a default if none was given. Ending with a meaningful exit code lets schedulers and other scripts know whether your job worked.
Quoting and common mistakes
| Mistake | What goes wrong | Fix |
|---|---|---|
name = value | Bash tries to run a command called name | No spaces: name=value |
Unquoted $file | File names with spaces split into several words | Always write "$file" |
No set -e | Script carries on after a failed step | Use set -euo pipefail and handle expected failures explicitly |
| Windows line endings | bad interpreter errors | Save with LF endings, or run dos2unix |
Tip: run shellcheck script.sh before you share a script. This free linter catches quoting bugs and risky patterns that are easy to miss by eye.
Practice
What does an exit code of 0 mean?
if uses this to decide which branch to run.Why write "$host" rather than $host?
What does set -u protect you from?
Key takeaways
- Start scripts with a shebang and set -euo pipefail.
- Quote variables with double quotes; use single quotes for literal text.
- if checks exit codes; 0 means success.
- Use functions and arguments with defaults to keep scripts reusable.
- Lint with shellcheck and move to Python when logic grows.
Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.