kae3g 9601: Shell Scripting Fundamentals - Variables, Conditionals, Loops
Phase 2: Core Systems & Tools | Week 6 | Reading Time: 16 minutes
Welcome to Phase 2! 🎉
Congratulations on completing Phase 1! You now understand the foundations of computing systems. Phase 2 builds on that knowledge by teaching you practical mastery of the tools and systems that power modern infrastructure.
We start with the shell - your most powerful interface to Unix systems.
What You'll Learn
- Why shell scripting is essential (automation!)
- Variables and quoting rules
- Conditionals (if/then/else)
- Loops (for, while, until)
- Exit codes and error handling
- Best practices for robust scripts
- When to use shell vs other languages
Prerequisites
- 9550: The Command Line - Basic shell usage
- 9560: Text Files - Understanding plain text
- 9570: Processes - How programs run
Why Shell Scripting?
The Unix philosophy (Essay 9510): "Do one thing well" + "Compose tools"
Shell scripts are the glue that combines simple tools into powerful workflows.
What Shell Scripts Do Best
Automation:
#!/bin/bash
# Deploy script - runs every time we ship
./run-tests.sh
./build-artifacts.sh
./upload-to-server.sh
./restart-services.sh
System Administration:
# Check disk space, clean up if needed
if [ $(df / | tail -1 | awk '{print $5}' | sed 's/%//') -gt 90 ]; then
    echo "Disk almost full! Cleaning..."
    find /tmp -type f -mtime +7 -delete
fi
Data Processing:
# Process logs: extract errors, count, email report
grep ERROR /var/log/app.log | \
    cut -d' ' -f3 | \
    sort | uniq -c | \
    mail -s "Error Report" admin@example.com
Quick Prototypes:
- Test an idea in 5 minutes
- Iterate rapidly
- Replace with "real" language later (if needed!)
Your First Script
The Shebang
#!/bin/bash
# This is a comment
echo "Hello, Valley Builder!"
First line (#!/bin/bash): Shebang
- Tells OS which interpreter to use
- /bin/bash= Bash shell
- Alternative: #!/bin/sh(POSIX shell, more portable)
Make it executable:
chmod +x hello.sh
./hello.sh
# Output: Hello, Valley Builder!
Variables
Basic Assignment
name="Ada Lovelace"
year=1842
echo "Hello, $name!"
echo "The year is $year"
Rules:
- No spaces around =
- Use $to access value
- Quotes recommended for strings
Quoting
Three types:
# Single quotes: Literal (no expansion)
echo 'My name is $name'
# Output: My name is $name
# Double quotes: Variable expansion
echo "My name is $name"
# Output: My name is Ada Lovelace
# No quotes: Word splitting + globbing
files=$HOME/*.txt
echo $files
# Output: /home/user/file1.txt /home/user/file2.txt
Best practice: Always quote variables!
# BAD (breaks on spaces)
file=$HOME/My Documents/file.txt
cat $file  # ERROR: tries to cat 3 files
# GOOD
file="$HOME/My Documents/file.txt"
cat "$file"  # Works!
Command Substitution
Run command, capture output:
# Old style (deprecated)
current_user=`whoami`
# New style (preferred)
current_user=$(whoami)
current_date=$(date +%Y-%m-%d)
echo "User: $current_user"
echo "Date: $current_date"
Conditionals
if/then/else
#!/bin/bash
age=25
if [ $age -ge 18 ]; then
    echo "Adult"
else
    echo "Minor"
fi
Syntax:
- if [ condition ]; then
- [ ]is the- testcommand
- -ge= "greater than or equal"
- fi= "if" backwards (closes block)
Test Operators
Numeric:
[ $a -eq $b ]  # Equal
[ $a -ne $b ]  # Not equal
[ $a -lt $b ]  # Less than
[ $a -le $b ]  # Less than or equal
[ $a -gt $b ]  # Greater than
[ $a -ge $b ]  # Greater than or equal
String:
[ "$a" = "$b" ]   # Equal (use = not ==!)
[ "$a" != "$b" ]  # Not equal
[ -z "$a" ]       # Empty string
[ -n "$a" ]       # Not empty
File:
[ -e "$file" ]  # Exists
[ -f "$file" ]  # Regular file
[ -d "$file" ]  # Directory
[ -r "$file" ]  # Readable
[ -w "$file" ]  # Writable
[ -x "$file" ]  # Executable
Multiple Conditions
# AND (both must be true)
if [ $age -ge 18 ] && [ $country = "USA" ]; then
    echo "Can vote in USA"
fi
# OR (either can be true)
if [ $age -lt 13 ] || [ $age -gt 65 ]; then
    echo "Discounted ticket"
fi
# NOT
if [ ! -f "$file" ]; then
    echo "File does not exist"
fi
elif (else if)
#!/bin/bash
score=85
if [ $score -ge 90 ]; then
    grade="A"
elif [ $score -ge 80 ]; then
    grade="B"
elif [ $score -ge 70 ]; then
    grade="C"
else
    grade="F"
fi
echo "Grade: $grade"
Loops
for Loop
Iterate over list:
#!/bin/bash
# Loop over words
for fruit in apple banana cherry; do
    echo "I like $fruit"
done
# Loop over files
for file in *.txt; do
    echo "Processing $file"
    wc -l "$file"
done
# Loop over numbers (Bash-specific)
for i in {1..5}; do
    echo "Count: $i"
done
C-style for loop (Bash):
for ((i=0; i<10; i++)); do
    echo "Number: $i"
done
while Loop
Repeat while condition true:
#!/bin/bash
count=1
while [ $count -le 5 ]; do
    echo "Count: $count"
    count=$((count + 1))
done
Read file line by line:
#!/bin/bash
while IFS= read -r line; do
    echo "Line: $line"
done < input.txt
Explanation:
- IFS=preserves whitespace
- read -rdisables backslash escaping
- < input.txtredirects file to stdin
until Loop
Repeat until condition true (opposite of while):
#!/bin/bash
count=1
until [ $count -gt 5 ]; do
    echo "Count: $count"
    count=$((count + 1))
done
Use case: Retry until success
#!/bin/bash
until ping -c 1 example.com &> /dev/null; do
    echo "Waiting for network..."
    sleep 1
done
echo "Network is up!"
Exit Codes
Every command returns an exit code:
- 0= success
- Non-zero = failure
Check last exit code:
ls /nonexistent
echo $?  # Prints: 2 (error)
ls /home
echo $?  # Prints: 0 (success)
Using Exit Codes
#!/bin/bash
if grep "ERROR" /var/log/app.log; then
    echo "Found errors in log"
    exit 1  # Signal failure
else
    echo "No errors found"
    exit 0  # Signal success
fi
Short-Circuit Operators
&& = "and then" (run if previous succeeded):
cd /tmp && rm tempfile
# Only runs rm if cd succeeds
|| = "or else" (run if previous failed):
mkdir /var/myapp || exit 1
# Exit if mkdir fails
Combine:
cd /project && make && make test && echo "Success!"
# Each step must succeed
Functions
Define reusable code:
#!/bin/bash
# Define function
greet() {
    echo "Hello, $1!"
}
# Call function
greet "Alice"
greet "Bob"
With return value:
#!/bin/bash
is_even() {
    local num=$1
    if [ $((num % 2)) -eq 0 ]; then
        return 0  # True (success)
    else
        return 1  # False (failure)
    fi
}
# Use in conditional
if is_even 4; then
    echo "4 is even"
fi
if is_even 7; then
    echo "7 is even"
else
    echo "7 is odd"
fi
Note: return sets exit code, not value. To return a value, use echo:
add() {
    echo $(($1 + $2))
}
result=$(add 3 5)
echo "3 + 5 = $result"
Error Handling
set -e (Exit on Error)
#!/bin/bash
set -e  # Exit immediately if any command fails
cd /project
make
make test
make install
echo "All steps succeeded!"
Without set -e: Script continues even if make fails!
set -u (Error on Undefined Variable)
#!/bin/bash
set -u  # Exit if using undefined variable
echo $UNDEFINED_VAR  # ERROR: unbound variable
Best practice: Start scripts with:
#!/bin/bash
set -euo pipefail
Explanation:
- -e: Exit on error
- -u: Error on undefined variable
- -o pipefail: Pipe fails if any command fails
trap (Cleanup on Exit)
#!/bin/bash
cleanup() {
    echo "Cleaning up..."
    rm -f /tmp/tempfile
}
trap cleanup EXIT  # Run cleanup on exit
# Script continues...
touch /tmp/tempfile
# ... do work ...
# cleanup() runs automatically on exit (success or failure!)
Try This
Exercise 1: Backup Script
Write a script that:
- Takes a directory path as argument
- Creates a timestamped backup (tar.gz)
- Stores in ~/backups/#!/bin/bash set -euo pipefail # Check argument if [ $# -ne 1 ]; then echo "Usage: $0 <directory>" exit 1 fi source_dir="$1" backup_dir="$HOME/backups" timestamp=$(date +%Y%m%d_%H%M%S) backup_file="$backup_dir/backup_$timestamp.tar.gz" # Create backup directory mkdir -p "$backup_dir" # Create backup echo "Backing up $source_dir..." tar -czf "$backup_file" "$source_dir" echo "Backup created: $backup_file"
Exercise 2: System Monitor
Write a script that:
- Checks CPU usage
- Checks disk usage
- Checks memory usage
- Alerts if any > 80%#!/bin/bash set -euo pipefail # Get metrics cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1) disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//') mem_usage=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100}') echo "CPU: ${cpu_usage}%" echo "Disk: ${disk_usage}%" echo "Memory: ${mem_usage}%" # Check thresholds threshold=80 if (( $(echo "$cpu_usage > $threshold" | bc -l) )); then echo "WARNING: High CPU usage!" fi if [ $disk_usage -gt $threshold ]; then echo "WARNING: High disk usage!" fi if [ $mem_usage -gt $threshold ]; then echo "WARNING: High memory usage!" fi
Exercise 3: Batch Rename
Write a script that:
- Renames all .txtfiles in current directory
- Changes spaces to underscores
- Converts to lowercase#!/bin/bash set -euo pipefail for file in *.txt; do # Skip if no files match [ -e "$file" ] || continue # Transform filename new_name=$(echo "$file" | tr ' ' '_' | tr '[:upper:]' '[:lower:]') # Rename if different if [ "$file" != "$new_name" ]; then mv -v "$file" "$new_name" fi done echo "Batch rename complete!"
Best Practices
1. Always Quote Variables
# BAD
cat $file
# GOOD
cat "$file"
Why: Prevents word splitting and globbing.
2. Use Meaningful Names
# BAD
x=10
f=$1
# GOOD
max_retries=10
config_file=$1
3. Check Arguments
#!/bin/bash
if [ $# -lt 1 ]; then
    echo "Usage: $0 <filename>"
    exit 1
fi
file="$1"
4. Use set -euo pipefail
Catch errors early, don't continue on failure.
5. Provide Help
#!/bin/bash
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    cat << EOF
Usage: $0 [OPTIONS] <file>
Options:
  -h, --help     Show this help
  -v, --verbose  Verbose output
Examples:
  $0 myfile.txt
  $0 -v myfile.txt
EOF
    exit 0
fi
6. Log Actions
#!/bin/bash
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}
log "Starting backup..."
# ... do backup ...
log "Backup complete!"
When NOT to Use Shell
Shell is great for:
- System administration
- Gluing tools together
- Quick automation
- Text processing (with sed/awk/grep)
Shell is bad for:
- Complex logic (use Python, Ruby, etc.)
- Data structures (arrays of arrays, hash maps)
- Performance-critical code
- Portability (Bash vs sh vs zsh...)
Rule of thumb: If script > 100 lines, consider a "real" language.
Going Deeper
Related Essays
- 9550: Command Line - Basic shell usage
- 9603: Shell Text Processing - grep, sed, awk (Coming Soon!)
- 9603: Shell Functions - Reusable scripts (Coming Soon!)
External Resources
- "Classic Shell Scripting" - Robbins & Beebe (definitive guide)
- ShellCheck - shellcheck.net(linter for shell scripts!)
- Bash Guide - mywiki.wooledge.org/BashGuide
- Advanced Bash Scripting Guide - TLDP
Reflection Questions
- Why prefer shell over Python for system tasks? (Speed to write, installed everywhere)
- When is quoting critical? (Filenames with spaces, preventing injection attacks)
- Why set -econtroversial? (Some argue it hides errors in conditionals—use carefully!)
- Should shell scripts have tests? (YES! Use BATS or shUnit2)
- How does shell relate to Nock? (Both are minimal, composable—shell glues Unix, Nock specifies computation!)
Summary
Shell scripting is:
- Automation (deploy, backup, monitor)
- Composition (combine simple tools)
- Speed (prototype in minutes)
- Universal (every Unix system has a shell)
Core concepts:
- Variables: name="value", always quote:"$name"
- Conditionals: if [ condition ]; then ... fi
- Loops: for item in list; do ... done,while [ condition ]; do ... done
- Exit codes: 0= success,$?= last exit code
- Functions: Reusable code blocks
- Error handling: set -euo pipefail,trap cleanup EXIT
Best practices:
- Quote variables
- Check arguments
- Use meaningful names
- Provide help (-h)
- Log actions
- Exit on error (set -e)
When to use:
- System administration ✅
- Quick automation ✅
- Text processing ✅
- Complex logic ❌ (use Python/Ruby/etc.)
In the Valley:
- Shell is glue (combines tools, like Unix philosophy!)
- Simple, composable (relates to Nock, functional programming)
- Ecological lens: "Shell scripts are the mycelium network—connecting distinct organisms (tools) into a functioning ecosystem."
Next: Essay 9603 - Shell Text Processing! We'll master grep, sed, and awk—the power trio for text manipulation!
Navigation:
 ← Previous: 9600 (Phase 1 Synthesis) | Phase 2 Index | Next: 9603 (Shell Text Processing) (New!)
Metadata:
- Phase: 2 (Core Systems & Tools)
- Week: 6 (Shell Scripting)
- Prerequisites: 9550, 9560, 9570
- Concepts: Variables, quoting, conditionals, loops, exit codes, functions, error handling
- Next Concepts: grep, sed, awk, text processing pipelines
- Plant Lens: Shell scripts as mycelium (connecting ecosystem), glue for composition
- Hands-On: 3 exercises (backup, monitor, batch rename)
Copyright © 2025 kae3g | Dual-licensed under Apache-2.0 / MIT
 Competitive technology in service of clarity and beauty