🧮 Lesson 8.2: Variables, Input, and Conditionals
Make your scripts dynamic — store data, ask questions, and make decisions.
🎯 Learning Objectives
- Create and use variables (with proper quoting)
- Work with environment variables and
export - Capture command output with
$() - Read user input with
read - Write
if/elif/elseconditionals - Use test expressions for strings, numbers, and files
Estimated Time: 45 minutes
📑 In This Lesson
Variables
Variables store data that your script can use and reuse. In Bash, variable assignment has one critical rule: no spaces around the =.
#!/bin/bash
# ✅ Correct — no spaces
NAME="Ray"
AGE=30
GREETING="Hello, $NAME"
# ❌ WRONG — spaces cause errors
# NAME = "Ray" → Bash thinks NAME is a command
# NAME= "Ray" → Also broken
# Use variables with $
echo "Name: $NAME"
echo "Age: $AGE"
echo "$GREETING"
# Curly braces for clarity (required when variable is next to other text)
FILE="report"
echo "${FILE}_final.txt" # report_final.txt
echo "$FILE_final.txt" # WRONG — Bash looks for variable $FILE_final
⚠️ The #1 Bash Beginner Mistake
Spaces around = in variable assignment will break your script. NAME="Ray" is assignment. NAME = "Ray" tries to run a command called NAME with arguments = and "Ray". This catches everyone at least once.
Quoting Rules
Quoting is one of the most important (and confusing) parts of Bash. Here's the cheat sheet:
| Quote Type | Expands Variables? | Use When |
|---|---|---|
"double quotes" | ✅ Yes | Most of the time — protects spaces while expanding variables |
'single quotes' | ❌ No | You want the literal text, no expansion at all |
| No quotes | ✅ Yes | Simple single-word values only (avoid for anything with spaces) |
NAME="Ray De La Paz"
echo "Hello, $NAME" # Hello, Ray De La Paz
echo 'Hello, $NAME' # Hello, $NAME (literal)
echo Hello, $NAME # Hello, Ray De La Paz (works, but fragile)
# Why double-quoting matters:
FILE="my document.txt"
cat $FILE # ❌ Bash sees: cat my document.txt (two arguments!)
cat "$FILE" # ✅ Bash sees: cat "my document.txt" (one argument)
🐧 Golden Rule of Bash
Always double-quote your variables unless you have a specific reason not to. "$VAR" prevents word splitting and glob expansion — two of the most common sources of bugs in shell scripts.
Environment Variables
Environment variables are inherited by child processes. Regular variables are not.
# Regular variable (only visible in this script/shell)
MY_VAR="local only"
# Environment variable (visible to child processes)
export MY_ENV_VAR="shared with children"
# One-liner: export at assignment
export API_KEY="abc123"
# View all environment variables
env
# or
printenv
# Some important built-in environment variables:
echo "$HOME" # Your home directory (/home/ray)
echo "$USER" # Current username (ray)
echo "$PATH" # Where Bash looks for commands
echo "$PWD" # Current working directory
echo "$SHELL" # Your default shell (/bin/bash)
echo "$HOSTNAME" # Machine name
echo "$RANDOM" # A random number (0-32767)
echo "$$" # Current process ID (PID)
MY_VAR=hello
export ENV_VAR=world"] --> B["Child Script"] B --> C["Can see ENV_VAR ✅
Cannot see MY_VAR ❌"] style A fill:#3b82f6,stroke:#2563eb,color:#fff style B fill:#6366f1,stroke:#4338ca,color:#fff style C fill:#f59e0b,stroke:#d97706,color:#fff
💡 Persistent Environment Variables
Variables set in a terminal session disappear when you close it. To make them permanent, add export lines to ~/.bashrc (loaded for every new interactive shell) or ~/.profile (loaded at login).
Command Substitution
Capture the output of a command and store it in a variable:
# Modern syntax (preferred): $(command)
TODAY=$(date +%Y-%m-%d)
echo "Today is $TODAY"
FILE_COUNT=$(ls | wc -l)
echo "There are $FILE_COUNT files here"
KERNEL=$(uname -r)
echo "Kernel: $KERNEL"
# Old syntax (backticks) — still works but harder to nest
TODAY=`date +%Y-%m-%d`
# Nesting is clean with $():
echo "Files modified today: $(find . -newer $(date +%Y-%m-%d) -type f | wc -l)"
# vs. backticks (messy with escaping):
# echo "Files: `find . -newer \`date +%Y-%m-%d\` -type f | wc -l`"
💡 Arithmetic
Bash can do integer math with $(( )):
A=10
B=3
echo "Sum: $((A + B))" # 13
echo "Product: $((A * B))" # 30
echo "Division: $((A / B))" # 3 (integer only!)
echo "Remainder: $((A % B))" # 1
# Increment
COUNT=0
((COUNT++))
echo "$COUNT" # 1
User Input with read
#!/bin/bash
# Basic input
echo -n "Enter your name: "
read NAME
echo "Hello, $NAME!"
# Prompt built into read (-p flag)
read -p "Enter your age: " AGE
echo "You are $AGE years old."
# Silent input for passwords (-s flag)
read -sp "Enter password: " PASSWORD
echo "" # Newline after silent input
echo "Password is ${#PASSWORD} characters long."
# Read with a timeout (-t seconds)
read -t 5 -p "Quick! Favorite color? " COLOR || echo "(Too slow!)"
# Read with a default value
read -p "Directory [/tmp]: " DIR
DIR="${DIR:-/tmp}" # Use /tmp if empty
echo "Using directory: $DIR"
# Read multiple values
read -p "Enter first and last name: " FIRST LAST
echo "First: $FIRST, Last: $LAST"
💡 Default Values with ${VAR:-default}
The ${VAR:-default} syntax returns default if $VAR is empty or unset. It's the standard way to provide fallback values:
NAME="${1:-World}" # First argument, or "World"
LOG_DIR="${LOG_DIR:-/var/log}" # Env var, or /var/log
echo "Hello, $NAME!"
Script Arguments
Scripts can accept arguments from the command line — no need for interactive prompts.
#!/bin/bash
# greet.sh — Greets the user by name
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
$ ./greet.sh Ray Developer
Script name: ./greet.sh
First argument: Ray
Second argument: Developer
All arguments: Ray Developer
Number of arguments: 2
| Variable | Meaning |
|---|---|
$0 | The script name itself |
$1, $2, … | First, second, … argument |
$@ | All arguments (each as a separate word) |
$* | All arguments (as a single string) |
$# | Number of arguments |
#!/bin/bash
# A script that requires exactly one argument
if [ $# -ne 1 ]; then
echo "Usage: $0 <filename>" >&2
exit 1
fi
echo "Processing file: $1"
if / elif / else
Conditionals let your script make decisions. The basic structure:
if [ condition ]; then
# commands if true
elif [ another_condition ]; then
# commands if this is true
else
# commands if nothing above was true
fi
⚠️ Spaces Matter Inside [ ]
The brackets [ ] are actually a command (shorthand for test). Spaces after [ and before ] are required:
# ✅ Correct
if [ "$NAME" = "Ray" ]; then
# ❌ WRONG — will error
if ["$NAME" = "Ray"]; then
#!/bin/bash
read -p "Enter a number: " NUM
if [ "$NUM" -gt 100 ]; then
echo "That's a big number!"
elif [ "$NUM" -gt 0 ]; then
echo "That's a positive number."
elif [ "$NUM" -eq 0 ]; then
echo "That's zero."
else
echo "That's a negative number."
fi
Test Expressions
String Comparisons
| Test | Meaning |
|---|---|
[ "$a" = "$b" ] | Strings are equal |
[ "$a" != "$b" ] | Strings are not equal |
[ -z "$a" ] | String is empty (zero length) |
[ -n "$a" ] | String is not empty |
Numeric Comparisons
| Test | Meaning |
|---|---|
[ "$a" -eq "$b" ] | Equal |
[ "$a" -ne "$b" ] | Not equal |
[ "$a" -gt "$b" ] | Greater than |
[ "$a" -ge "$b" ] | Greater than or equal |
[ "$a" -lt "$b" ] | Less than |
[ "$a" -le "$b" ] | Less than or equal |
File Tests
| Test | Meaning |
|---|---|
[ -e "$f" ] | File exists (any type) |
[ -f "$f" ] | Is a regular file |
[ -d "$f" ] | Is a directory |
[ -r "$f" ] | Is readable |
[ -w "$f" ] | Is writable |
[ -x "$f" ] | Is executable |
[ -s "$f" ] | File exists and is not empty |
Combining Tests
# AND (both must be true)
if [ "$AGE" -ge 18 ] && [ "$AGE" -le 65 ]; then
echo "Working age"
fi
# OR (either can be true)
if [ "$COLOR" = "red" ] || [ "$COLOR" = "blue" ]; then
echo "Primary color!"
fi
# NOT (invert)
if [ ! -f "$FILE" ]; then
echo "File does not exist"
fi
🐧 [[ ]] — The Bash Upgrade
Bash offers [[ ]] (double brackets) with extra features: pattern matching, regex, and safer quoting. If you're writing Bash-specific scripts (not POSIX sh), prefer [[ ]]:
# Pattern matching (globbing)
if [[ "$FILE" == *.txt ]]; then
echo "It's a text file"
fi
# Regex matching
if [[ "$EMAIL" =~ ^[a-zA-Z]+@[a-zA-Z]+\.[a-zA-Z]+$ ]]; then
echo "Looks like an email"
fi
# No word splitting — no need to quote variables (but still good practice)
if [[ $NAME = "Ray" ]]; then echo "Hi Ray"; fi
Practical Example
Here's a script that combines everything — a file backup utility with input validation:
#!/bin/bash
# ============================================
# backup.sh — Back up a directory with a timestamp
# Usage: ./backup.sh [source_directory]
# ============================================
# Get source from argument or ask
SOURCE="${1:-}"
if [ -z "$SOURCE" ]; then
read -p "Directory to back up: " SOURCE
fi
# Validate the source
if [ ! -d "$SOURCE" ]; then
echo "ERROR: '$SOURCE' is not a directory." >&2
exit 1
fi
# Set up the backup
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_$(basename "$SOURCE")_${TIMESTAMP}.tar.gz"
DEST_DIR="${HOME}/backups"
# Create destination if needed
if [ ! -d "$DEST_DIR" ]; then
mkdir -p "$DEST_DIR"
echo "Created backup directory: $DEST_DIR"
fi
# Perform the backup
echo "Backing up '$SOURCE' → '$DEST_DIR/$BACKUP_NAME'"
tar -czf "$DEST_DIR/$BACKUP_NAME" -C "$(dirname "$SOURCE")" "$(basename "$SOURCE")"
# Check if it worked
if [ $? -eq 0 ]; then
SIZE=$(du -h "$DEST_DIR/$BACKUP_NAME" | awk '{print $1}')
echo "✅ Backup complete! Size: $SIZE"
exit 0
else
echo "❌ Backup failed!" >&2
exit 1
fi
Exercises
🏋️ Exercise 1: Variable Practice
Create a script called myinfo.sh that:
- Stores your name, favorite color, and OS in variables
- Captures the current date with command substitution
- Prints all four values in a formatted message
- Uses
${VAR}syntax at least once
🏋️ Exercise 2: Interactive Greeter
Create greeter.sh that:
- Asks for the user's name (with a default of "friend")
- Asks for the current hour (0-23)
- Prints "Good morning", "Good afternoon", or "Good evening" based on the hour
- Handles invalid input (non-numeric, out of range)
🏋️ Exercise 3: File Checker
Create filecheck.sh that takes a filename as an argument and reports:
- Whether it exists
- Whether it's a file or directory
- Whether it's readable, writable, and executable
- If no argument is given, print a usage message and exit with code 1
🏋️ Exercise 4: Number Guessing Game
Create guess.sh — a number guessing game:
- Generate a random number 1-100:
TARGET=$((RANDOM % 100 + 1)) - Ask the user to guess
- Tell them if the guess is too high, too low, or correct
- Count the number of guesses
- Hint: You'll need a loop — preview of the next lesson! Use
while true; do ... done
Knowledge Check
❓ Question 1
What happens if you write NAME = "Ray" (with spaces)?
❓ Question 2
What does ${DIR:-/tmp} do?
❓ Question 3
Which test checks if a file exists and is a regular file?
❓ Question 4
What does $# contain inside a script?
Summary
🎉 Key Takeaways
- Variables:
NAME="value"(no spaces!). Access with$NAMEor${NAME} - Always double-quote variables:
"$VAR"prevents word splitting bugs export VARmakes variables visible to child processes$(command)captures command output in a variableread -p "Prompt: " VARgets user input;${VAR:-default}provides fallbacks$1,$2,$@,$#handle script argumentsif [ condition ]; then ... fifor decisions (mind the spaces!)- Use
-eq/-gt/-ltfor numbers,=/!=for strings,-f/-d/-efor files
🍎 On macOS
All the variable and conditional syntax in this lesson works with #!/bin/bash scripts on macOS. One caveat: macOS ships with Bash 3.2 (from 2007) due to licensing. This means some newer Bash 4+ features (associative arrays, readarray) won't work unless you install a newer Bash via brew install bash. Everything in this lesson uses Bash 3-compatible syntax, so you're fine.
🚀 What's Next?
The final lesson covers Loops and Functions — repeating actions and organizing your code into reusable pieces.