Have you ever had to rename 200 files one by one? Or run the same command on dozens of servers every morning? If you're a developer or sysadmin, this scenario is all too familiar. Here at Meteora Web, we've been there countless times: a repetitive task that eats your time, energy, and focus. The answer is a well-written Bash script. In this guide, we take Bash's four core building blocks — variables, conditions, loops, and functions — and turn them into concrete tools to automate your work. No abstract theory: real examples, common mistakes, code you can copy and use right away.
Variables: Where You Store Changing Data
A variable in Bash is a text container (yes, numbers are also text). You use it to avoid duplicating values and to make your script adaptable to different contexts.
Declaration and Assignment
#!/bin/bash
# Assign a value to a variable (NO spaces around =)
NAME="Meteora"
YEAR=2017
PI=3.14
echo "Company: $NAME, founded in $YEAR"
Common mistake: writing NAME = "Meteora" with spaces. Bash will interpret NAME as a command and = as its first argument. Always no spaces.
Environment vs Local Variables
Use export to make a variable available to child processes. For temporary variables, just declare them inside the script.
# Local variable
DB_HOST="localhost"
# Environment variable (visible to subsequent commands)
export DB_USER="admin"
Expansion and Quoting
- Double quotes
"$VAR"— allow variable expansion. - Single quotes
'$VAR'— treat everything literally, no expansion. - Backticks or $() — execute a command and capture its output:
NOW=$(date).
NAME="Meteora Web"
echo "Welcome to $NAME" # prints: Welcome to Meteora Web
echo 'Welcome to $NAME' # prints: Welcome to $NAME
Action step:
Open a terminal and create a script with a variable that holds a directory path. Use it to run ls -l on that directory. Then change the variable's value and rerun the script — you'll immediately see the benefit.
Conditions: Let the Script Decide
A script without conditions is like a traffic light that's always green: it goes straight, even when it should stop. With if, elif, else and test operators, the script makes choices based on data.
Basic if Structure
if [ "$NUM" -gt 10 ]; then
echo "Number greater than 10"
elif [ "$NUM" -eq 10 ]; then
echo "Number equals 10"
else
echo "Number less than 10"
fi
Note the space after [ and before ]; it's mandatory. And then must be on the same line as if separated by semicolon, or on a new line.
Test Operators
- Numeric comparison:
-eq,-ne,-gt,-ge,-lt,-le. - Strings:
=(equal),!=(not equal),-z(zero length),-n(non-empty). - Files:
-f(exists and is a regular file),-d(directory),-x(executable).
FILE="/etc/passwd"
if [ -f "$FILE" ]; then
echo "$FILE exists"
fi
Advanced Tests with [[ ]] (Recommended)
[[ ... ]] is more powerful: supports logical operators && and ||, pattern matching, and doesn't require quoting variables.
if [[ "$NAME" == M* ]]; then
echo "Name starts with M"
fi
Common mistake: using && inside [ ] to combine conditions. In [ ] you should use -a (and) and -o (or), but they're deprecated. Always use [[ ]] for better readability.
Action step:
Write a script that takes an argument (e.g., a directory name) and checks if it exists. If it does, print the number of files inside; otherwise, create it. We use this constantly in deployments.
Loops: Repeat Without Going Crazy
Loops let you execute a block of code over a list of items. Bash gives you two main players: for and while.
for Loop: on Explicit Lists or Wildcards
# Iterate over a list of words
for FRUIT in apple pear banana; do
echo "Fruit: $FRUIT"
done
# Iterate over files matching a pattern
for FILE in *.log; do
echo "Cleaning $FILE"
> "$FILE" # empty the file
done
for Loop with Sequences
for i in {1..5}; do
echo "Iteration $i"
done
Beware: {1..5} doesn't work with variables (e.g., {1..$N}). For dynamic ranges use seq.
while Loop: As Long as a Condition is True
COUNTER=1
while [ $COUNTER -le 5 ]; do
echo "Counter: $COUNTER"
((COUNTER++))
done
The (( )) syntax enables integer arithmetic. You can also use let or $(( )).
Reading a File Line by Line
while IFS= read -r LINE; do
echo "Processing: $LINE"
done < "input.txt"
Common mistake: forgetting IFS= and -r. Without them, Bash trims whitespace and interprets backslashes. Always use this pattern for safe file reading.
Action step:
Take a directory full of .jpg files and write a for loop that converts them to .png using convert (ImageMagick). A batch backup that saves you hours.
Functions: Organize and Reuse Code
When a script exceeds 30 lines, chaos is around the corner. Functions encapsulate logical blocks: you call them when needed, modify them in one place.
Definition and Call
#!/bin/bash
log() {
local MESSAGE="$1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $MESSAGE"
}
log "Starting deploy"
log "Deploy finished"
The function keyword is optional; just the name followed by parentheses and a body in braces. local makes a variable visible only inside the function, avoiding conflicts with the main script.
Parameters and Return Values
sum() {
local A=$1
local B=$2
local RESULT=$((A + B))
echo $RESULT
}
RESULT=$(sum 5 7)
echo "5 + 7 = $RESULT"
Bash functions don't return values with return (that's only for exit codes). Actual output is produced with echo and captured with $().
Common Mistakes
- Forgetting
local— variables become global and pollute the script. - Using
returnto return data — works only for integer 0-255. For strings or large numbers, useechoand capture output. - Not quoting parameters containing spaces — always use
"$1".
Action step:
Take the for loop script from the directory example. Extract the conversion logic into a function convert_format that accepts source and destination extensions. Then call it for .jpg→.png and .png→.webp. A script that scales well.
In Summary — What to Do Now
- Write a first backup script using variables for source and destination directories, a for loop to iterate over files, and a function to log every operation.
- Replace repetitive manual commands with a Bash script. Identify a task you do daily (e.g., updating file permissions, compiling assets) and automate it.
- Test file conditions in your deployment scripts: verify that binaries exist, directories are writable, before running dangerous operations.
- Use
[[ ]]instead of[ ]for all new conditions. More readable, fewer bugs. - Put each function in a separate library file (e.g.,
lib.sh) and load it withsource lib.sh. Pure reuse.
Here at Meteora Web, we use Bash every day in our deployment workflows, server security checks, and VM management. It's the sysadmin's Swiss Army knife, and mastering it takes a massive weight off your shoulders. Pick a repetitive problem, write a script, and see the difference for yourself.
Sponsored Protocol