Variables and Quoting#

Concepts#

Variables#

Variables store values (text or numbers) that you can use later.

Setting Variables#

name="Alice"               # string (no spaces around `=` !)
age=30                      # number (stored as string internally)
path="/var/log"             # path
greeting="Hello, $name"    # variable expansion inside double quotes

Critical rule: No spaces around =. This is wrong and will fail:

name = "Alice"     # WRONG — bash interprets "name" as a command

Using Variables#

Prefix with $ or wrap in ${}:

echo $name                  # Alice
echo "$name is $age"        # Alice is 30
echo "${name}_backup"       # Alice_backup (braces clarify the boundary)
echo "$name_backup"         # WRONG — looks for variable $name_backup

Use ${} when the variable name is adjacent to other text. It is always safe to use ${}.

Unsetting Variables#

unset name                  # remove the variable
echo $name                  # empty

Quoting#

Quoting controls how the shell interprets special characters. This is one of the most important (and confusing) topics in shell scripting.

Double Quotes "..." — Weak Quoting#

Variables and command substitution are expanded. Spaces are preserved.

name="Alice"
echo "Hello, $name"           # Hello, Alice  (variable expanded)
echo "Today is $(date)"       # Today is Wed Oct 15...  (command expanded)
echo "Files: $(ls)"           # command substitution works
echo "Path: $HOME/docs"       # variable expanded

Always double-quote variables to prevent word splitting and globbing:

file="my document.txt"
cat $file                     # WRONG — tries to open "my" and "document.txt"
cat "$file"                   # RIGHT — opens "my document.txt"

Single Quotes '...' — Strong Quoting#

Nothing is expanded. Everything is treated literally.

echo 'Hello, $name'           # Hello, $name  (literal $name)
echo 'Today is $(date)'       # Today is $(date)  (literal)
echo 'No expansion: \n $HOME' # No expansion: \n $HOME  (literal)

Use single quotes when you want the exact text, no interpretation.

Backticks `...` — Command Substitution (Old Style)#

echo "Today is `date`"        # same as $(date), but harder to nest
# Prefer $() as it nests: echo "$(echo "$(date)")"

No Quotes — Dangerous#

Without quotes, the shell performs word splitting and globbing:

files="*.txt"
echo $files                   # expands *.txt to actual filenames!
echo "$files"                 # prints: *.txt (literal)

message="hello    world"
echo $message                 # hello world (extra spaces removed)
echo "$message"               # hello    world (preserved)

Rule of thumb: Always double-quote "$variables" unless you have a specific reason not to.

Environment Variables vs Local Variables#

  • Local variables exist only in the current shell/script:

    myvar="local"
    
  • Environment variables are exported and available to child processes:

    export MY_ENV_VAR="exported"
    
# Set and export in one line
export DATABASE_URL="postgres://localhost:5432/mydb"

# Check if a variable is exported
env | grep MY_ENV_VAR

# Common environment variables
echo $HOME          # /home/kmiguel
echo $USER          # kmiguel
echo $SHELL         # /bin/bash
echo $PATH          # command search path
echo $PWD           # current directory
echo $LANG          # locale settings
echo $EDITOR        # preferred text editor
echo $TERM          # terminal type

Arithmetic#

Bash variables are strings. For arithmetic, use $(( )):

a=5
b=3

echo $((a + b))       # 8
echo $((a - b))       # 2
echo $((a * b))       # 15
echo $((a / b))       # 1 (integer division!)
echo $((a % b))       # 2 (remainder)
echo $((a ** 2))      # 25 (exponentiation)

# Increment
((a++))
echo $a               # 6

# Assignment
result=$((a * b + 2))
echo $result

Note: Bash only does integer arithmetic. For floating-point, use bc:

echo "scale=2; 10 / 3" | bc    # 3.33
echo "scale=4; 22 / 7" | bc    # 3.1428

Parameter Expansion#

Bash provides powerful ways to manipulate variables:

name="Alice"
path="/home/alice/documents/report.txt"

# Default values
echo ${unset_var:-"default"}     # prints "default" (var is not set)
echo ${unset_var:="default"}     # sets var to "default" AND prints it

# String length
echo ${#name}                    # 5

# Substring
echo ${name:0:3}                 # Ali (from position 0, length 3)
echo ${name:2}                   # ice (from position 2 to end)

# Remove pattern from front
echo ${path#*/}                  # home/alice/documents/report.txt (shortest match)
echo ${path##*/}                 # report.txt (longest match — like basename)

# Remove pattern from back
echo ${path%/*}                  # /home/alice/documents (shortest match — like dirname)
echo ${path%%/*}                 # (empty — longest match)

# Substitution
echo ${name/Alice/Bob}           # Bob (replace first occurrence)
echo ${path//./_}                # all dots replaced with underscores

# Uppercase / lowercase (Bash 4+)
echo ${name^^}                   # ALICE
echo ${name,,}                   # alice
echo ${name^}                    # Alice (capitalize first letter)

Arrays#

# Create an array
fruits=("apple" "banana" "cherry" "date")

# Access elements (0-indexed)
echo ${fruits[0]}            # apple
echo ${fruits[2]}            # cherry

# All elements
echo ${fruits[@]}            # apple banana cherry date

# Number of elements
echo ${#fruits[@]}           # 4

# Add an element
fruits+=("elderberry")

# Loop over array
for fruit in "${fruits[@]}"; do
    echo "Fruit: $fruit"
done

# Array indices
echo ${!fruits[@]}           # 0 1 2 3 4

# Slice
echo ${fruits[@]:1:2}        # banana cherry (from index 1, length 2)

Lab#

Exercise 1: Variables and Expansion#

cd ~/lab/scripts

cat > variables.sh << 'SCRIPT'
#!/bin/bash

# Setting variables
greeting="Hello"
name="World"
count=42

# Using variables
echo "$greeting, $name!"
echo "The count is: $count"
echo "${greeting}_${name}"

# Demonstrating the need for braces
item="file"
echo "Looking for ${item}s..."
echo "Looking for $items..."    # WRONG — $items is undefined

# Environment variables
echo "Home: $HOME"
echo "User: $USER"
echo "Shell: $SHELL"
SCRIPT

chmod +x variables.sh
./variables.sh

Exercise 2: Quoting Differences#

cd ~/lab/scripts

cat > quoting.sh << 'SCRIPT'
#!/bin/bash

name="Alice"
path="/tmp/test dir/file.txt"

echo "=== Double Quotes ==="
echo "Hello, $name"
echo "Date: $(date +%Y-%m-%d)"
echo "Home: $HOME"

echo ""
echo "=== Single Quotes ==="
echo 'Hello, $name'
echo 'Date: $(date +%Y-%m-%d)'
echo 'Home: $HOME'

echo ""
echo "=== No Quotes (Dangerous) ==="
msg="hello    world    foo"
echo $msg      # spaces collapsed
echo "$msg"    # spaces preserved

echo ""
echo "=== Quoting Filenames ==="
echo "With quotes: '$path' (safe)"
# Without quotes, the space would split it into two arguments
SCRIPT

chmod +x quoting.sh
./quoting.sh

Exercise 3: Arithmetic#

cd ~/lab/scripts

cat > math.sh << 'SCRIPT'
#!/bin/bash

a=15
b=4

echo "a = $a, b = $b"
echo "a + b = $((a + b))"
echo "a - b = $((a - b))"
echo "a * b = $((a * b))"
echo "a / b = $((a / b))  (integer division)"
echo "a % b = $((a % b))  (remainder)"
echo "a^2   = $((a ** 2))"

# Increment
((a++))
echo "After a++: a = $a"

# Floating point with bc
echo "Precise division: $(echo "scale=2; 15 / 4" | bc)"
SCRIPT

chmod +x math.sh
./math.sh

Exercise 4: Parameter Expansion#

cd ~/lab/scripts

cat > expansion.sh << 'SCRIPT'
#!/bin/bash

filename="/home/user/documents/report_final.pdf"

echo "Full path: $filename"
echo "Filename:  ${filename##*/}"          # report_final.pdf
echo "Directory: ${filename%/*}"           # /home/user/documents
echo "Extension: ${filename##*.}"          # pdf
echo "No ext:    ${filename%.*}"           # /home/user/documents/report_final

# Default values
echo "DB_HOST is: ${DB_HOST:-localhost}"   # localhost (not set)
echo "HOME is:    ${HOME:-not set}"        # /home/... (it IS set)

# String operations
text="Hello World"
echo "Length:     ${#text}"                # 11
echo "Upper:     ${text^^}"               # HELLO WORLD
echo "Lower:     ${text,,}"               # hello world
echo "Replace:   ${text/World/Linux}"     # Hello Linux
SCRIPT

chmod +x expansion.sh
./expansion.sh

Exercise 5: Arrays#

cd ~/lab/scripts

cat > arrays.sh << 'SCRIPT'
#!/bin/bash

# Define an array
colors=("red" "green" "blue" "yellow" "purple")

echo "First color: ${colors[0]}"
echo "Third color: ${colors[2]}"
echo "All colors: ${colors[@]}"
echo "Count: ${#colors[@]}"

# Add an element
colors+=("orange")
echo "After adding: ${colors[@]}"

# Loop
echo ""
echo "Listing colors:"
for color in "${colors[@]}"; do
    echo "  - $color"
done

# Build an array from command output
logfiles=($(ls /var/log/*.log 2>/dev/null))
echo ""
echo "Found ${#logfiles[@]} .log files in /var/log"
SCRIPT

chmod +x arrays.sh
./arrays.sh

Review#

1. Why must there be no spaces around `=` when setting a variable?

Bash interprets name = "value" as running a command called name with arguments = and "value". The assignment name="value" (no spaces) is the correct syntax.

2. What is the difference between single quotes and double quotes?

Double quotes "..." allow variable expansion ($var) and command substitution ($(cmd)). Single quotes '...' treat everything literally — no expansion occurs.

3. Why should you always double-quote variables?

Without quotes, the shell performs word splitting (spaces in values become separate arguments) and globbing (* is expanded to matching filenames). Quoting prevents these unintended behaviors.

4. How do you do arithmetic in Bash?

Use $(( )): result=$((5 + 3)). For floating-point, pipe to bc: echo "scale=2; 10/3" | bc.

5. What does `${var:-default}` do?

If $var is unset or empty, it expands to default. If $var has a value, it expands to that value. This provides a fallback without modifying the variable.

6. How do you extract the filename from a full path using parameter expansion?

${path##*/} — this removes the longest match of */ from the front, leaving just the filename. For example, /home/user/file.txt becomes file.txt.

7. What is the difference between `$@` and `$*`?

Both expand to all arguments. "$@" (quoted) preserves each argument as a separate word — this is almost always what you want. "$*" (quoted) joins all arguments into a single string separated by the first character of $IFS.


Previous: Your First Script | Next: Conditionals