Stage 2 · Tools
Bash Fundamentals
Variables & Expansions
Variable types, parameter expansion, arithmetic, and quoting — mastering Bash data manipulation.
Variable Assignment
Variables in Bash are assigned without spaces around the equals sign. The syntax is VAR=value. Adding spaces creates a command invocation instead of an assignment.
NAME="Alice" # Correct — no spaces
GREETING=Hello # Also correct
# NAME = "Alice" # WRONG — this tries to run a command called NAME
# NAME =" Alice" # WRONG — this tries to run a command with = as first argumentThis is one of the most common Bash mistakes. VAR=value is assignment. VAR = value is a command call. Always use no spaces around =.
NAME=Alice assigns a variable. NAME = Alice tries to run a command called NAME with arguments = and Alice. This trips up every beginner at least once.
Variable Types
Bash has several types of variables, each with different scope and behavior. Understanding these differences prevents subtle bugs in your scripts.
| Type | Syntax | Scope | Example |
|---|---|---|---|
| Local | VAR=value | Current shell only | NAME="Alice" |
| Exported | export VAR=value | Current shell + children | export PATH=... |
| Readonly | readonly VAR=value | Cannot be changed | readonly PI=3.14 |
| Special | $$, $?, $! | Set by shell | echo $$ |
OUTER="visible here"
child_function() {
echo "Child sees: $OUTER" # Works — exported or inherited
INNER="set by child"
}
child_function
echo "Parent sees: $INNER" # Empty — INNER was never exportedVariables are local to the shell by default. A child process (function call) inherits exported variables but cannot set variables visible to the parent.
Parameter Expansion
Parameter expansion is Bash's way of manipulating strings and variables without external commands. It is faster and cleaner than piping through sed or awk for simple transformations.
FILE="/var/log/app/error.log"
echo "${FILE##*/}" # error.log — remove longest prefix match
echo "${FILE%/*}" # /var/log/app — remove shortest suffix match
echo "${FILE%.log}" # /var/log/app/error — remove .log suffix
echo "${FILE##*.}" # log — extract extension
echo "${FILE/old/new}" # Replace first occurrence
echo "${FILE//o/0}" # Replace all occurrences (o -> 0)## removes the longest matching prefix from the front. % removes the shortest matching suffix from the back. ##* and %/* are the most commonly used patterns for extracting filenames and directories.
{VAR:-default} returns the variable's value if set and non-empty, otherwise returns 'default'. Use {VAR:=default} to assign the default value. Use {VAR:?error} to exit with an error if the variable is unset.
Arithmetic Expansion
Bash can perform integer arithmetic using the $(( )) syntax. This is useful for counters, array indices, and simple calculations without invoking external tools like bc or expr.
A=10
B=3
echo "$((A + B))" # 13
echo "$((A - B))" # 7
echo "$((A * B))" # 30
echo "$((A / B))" # 3 (integer division)
echo "$((A % B))" # 1 (modulo)
echo "$((A ** 2))" # 100 (exponent)Bash arithmetic is integer-only. For floating-point math, use bc or awk. The $(()) syntax is preferred over the older expr syntax which requires escaping special characters.
COUNT=0
COUNT=$((COUNT + 1)) # 1
((COUNT++)) # 2 — post-increment
((++COUNT)) # 3 — pre-increment
echo "$COUNT"The (( )) syntax evaluates an arithmetic expression. ++ and -- work like in C. This is the idiomatic way to increment counters in Bash.
Quoting Rules
Quoting controls how the shell handles expansion. Single quotes prevent all expansion. Double quotes allow variable and command substitution but prevent word splitting and globbing. No quotes allow everything.
FILES="file1.txt file2.txt"
# No quotes — word splitting and globbing
echo $FILES # file1.txt file2.txt (two words)
# Double quotes — preserve as single argument
echo "$FILES" # file1.txt file2.txt (one argument)
# Single quotes — literal, no expansion
echo '$FILES' # $FILES (literal text)
# Command substitution in quotes
echo "Today is $(date +%F)" # Today is 2025-01-15
echo 'Today is $(date +%F)' # Today is $(date +%F)Always double-quote variables unless you specifically need word splitting or globbing. This prevents bugs from filenames with spaces, empty variables, or unexpected glob matches.
The number one source of bugs in shell scripts is unquoted variables. $VAR without quotes can break on spaces, glob, or expand to nothing. Always use "$VAR" unless you have a specific reason not to.
Arrays
Bash supports indexed arrays and associative arrays. Arrays let you group related values and iterate over them cleanly.
FRUITS=("apple" "banana" "cherry")
echo "${FRUITS[0]}" # apple
echo "${FRUITS[2]}" # cherry
echo "${#FRUITS[@]}" # 3 — array length
FRUITS+=("date") # Append
echo "${FRUITS[@]}" # apple banana cherry date
for fruit in "${FRUITS[@]}"; do
echo "I like $fruit"
doneArray indices start at 0. {FRUITS[@]} expands all elements. {#FRUITS[@]} gives the length. The += operator appends to an array.
declare -A COLORS
COLORS[red]="#ff0000"
COLORS[green]="#00ff00"
COLORS[blue]="#0000ff"
echo "${COLORS[red]}" # #ff0000
echo "${!COLORS[@]}" # red green blue — list keys
for key in "${!COLORS[@]}"; do
echo "$key = ${COLORS[$key]}"
doneAssociative arrays require declare -A. They work like hash maps or dictionaries. Use them for mapping names to values, like config options or color codes.
Dash and plain sh do not support arrays. If you need portability, use space-separated strings and the for var in $LIST pattern (unquoted) or store items one per line and use a while-read loop.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.