Shell Scripting Basics: Bash Variables, Conditionals, Loops, Functions, and Error Handling
Master Bash shell scripting including variables, conditionals (if/case), loops (for/while), functions, command substitution, arrays, error handling, and practical examples.
Shell Scripting Basics: Bash Variables, Conditionals, Loops, Functions, and Error Handling
Shell scripting is the cornerstone of Linux automation. Mastering Bash enables you to automate repetitive tasks, create powerful tools, and improve system administration efficiency.
Getting Started
Creating a Script
# Create script file
nano my_script.sh
# Add shebang (tells system which interpreter to use)
#!/bin/bash
# Add executable permission
chmod +x my_script.sh
# Run script
./my_script.sh
# Or run with bash explicitly
bash my_script.sh
# Run script from PATH
# Place in /usr/local/bin/
sudo cp my_script.sh /usr/local/bin/
# Now run from anywhere
my_script.shComments
# This is a comment
# Comments are ignored by interpreter
echo "This runs" # Inline commentVariables
Creating and Using Variables
# Create variable (no spaces around =)
NAME="John"
AGE=30
GREETING="Hello, $NAME"
# Use variable
echo $NAME
echo ${NAME} # Safer syntax
# Output
# John
# John
# Append to variable
PATH=$PATH:/opt/custom/bin
# Multi-line variable
MESSAGE="Line 1
Line 2
Line 3"
# Prevent expansion
LITERAL='$NAME is not expanded'
echo $LITERAL
# $NAME is not expanded
# Variable inside double quotes (expanded)
EXPANDED="$NAME is expanded"
echo $EXPANDED
# John is expandedVariable Types
# String
NAME="Alice"
# Number
COUNT=42
# Array
FRUITS=("apple" "banana" "orange")
echo ${FRUITS[0]} # apple
echo ${FRUITS[@]} # all elements
# Associative array (map/dictionary)
declare -A COLORS
COLORS[red]="#FF0000"
COLORS[green]="#00FF00"
echo ${COLORS[red]}
# Readonly variable
readonly VERSION="1.0"
# Unset variable
unset VARIABLE
# Environment variable (export)
export API_KEY="secret"Variable Expansion
# Default value
echo ${UNDEFINED:-"default"}
# Assign default if undefined
echo ${UNDEFINED:="default"}
# Show error if undefined
echo ${UNDEFINED:?Error: variable not set}
# Use alternate value if set
echo ${NAME:+"Value is: $NAME"}
# Substring
STRING="Hello World"
echo ${STRING:0:5} # Hello
echo ${STRING:6} # World
# String replacement
echo ${STRING/World/Linux}
echo ${STRING//o/O} # Replace all
# Remove pattern
echo ${STRING%World} # Remove from end
echo ${STRING#Hello } # Remove from start
# Get length
echo ${#STRING} # 11
# Uppercase/lowercase
echo ${NAME^^} # Uppercase
echo ${NAME,,} # Lowercase (Bash 4+)Command Substitution
# Capture command output
RESULT=$(echo "Hello")
RESULT=`echo "Hello"` # Older syntax
# Store command output
FILES=$(ls -la)
CURRENT_DATE=$(date +%Y-%m-%d)
USER_HOME=$(echo $HOME)
# Use in variables
echo "Files: $FILES"
echo "Date: $CURRENT_DATE"
# Nested substitution
YEAR=$(date +%Y)
echo "Year: $YEAR"
# Use directly in commands
echo "Current time: $(date)"Input and Output
Reading Input
# Read from user
read NAME
echo "Hello, $NAME"
# Prompt for input
read -p "Enter name: " NAME
echo "Hello, $NAME"
# Read multiple variables
read FIRSTNAME LASTNAME
echo "$FIRSTNAME $LASTNAME"
# Read into array
read -a ARRAY
echo ${ARRAY[0]}
# Silent input (password)
read -sp "Enter password: " PASSWORD
echo
# Read with timeout
read -t 5 -p "Enter name (5 sec): " NAME
# Read file line by line
while read LINE; do
echo "Line: $LINE"
done < myfile.txtOutput
# Simple output
echo "Hello World"
# No newline
echo -n "Hello"
# Escaped characters
echo "Line 1\nLine 2"
echo -e "Line 1\nLine 2" # Enable interpretation
# Multiple variables
echo $VAR1 $VAR2 $VAR3
# Formatted output
printf "Name: %s, Age: %d\n" "$NAME" "$AGE"
# Append to file
echo "Log entry" >> logfile.txt
# Redirect to file
echo "Output" > output.txt
# Error output
echo "Error message" >&2
# Silence output
command > /dev/null 2>&1Conditionals
if/elif/else
# Simple if
if [ $AGE -gt 18 ]; then
echo "Adult"
fi
# if/else
if [ $COUNT -eq 5 ]; then
echo "Count is 5"
else
echo "Count is not 5"
fi
# if/elif/else
if [ $GRADE = "A" ]; then
echo "Excellent"
elif [ $GRADE = "B" ]; then
echo "Good"
elif [ $GRADE = "C" ]; then
echo "Average"
else
echo "Below average"
fi
# Multiple conditions (AND)
if [ $AGE -gt 18 ] && [ $AGE -lt 65 ]; then
echo "Working age"
fi
# Multiple conditions (OR)
if [ "$OS" = "Linux" ] || [ "$OS" = "Darwin" ]; then
echo "Unix-like system"
fi
# Negate condition
if [ ! -f "file.txt" ]; then
echo "File doesn't exist"
fiTest Conditions
# String tests
[ -z "$STRING" ] # Empty string
[ -n "$STRING" ] # Not empty
[ "$STR1" = "$STR2" ] # Equal
[ "$STR1" != "$STR2" ] # Not equal
# Numeric tests
[ $NUM -eq 5 ] # Equal
[ $NUM -ne 5 ] # Not equal
[ $NUM -gt 5 ] # Greater than
[ $NUM -ge 5 ] # Greater or equal
[ $NUM -lt 5 ] # Less than
[ $NUM -le 5 ] # Less or equal
# File tests
[ -e "file" ] # Exists
[ -f "file" ] # Regular file
[ -d "dir" ] # Directory
[ -r "file" ] # Readable
[ -w "file" ] # Writable
[ -x "file" ] # Executable
[ -s "file" ] # Non-empty
# Logical operators
[ $A -gt 0 ] && [ $B -lt 10 ]
[ $A -eq 0 ] || [ $B -eq 0 ]
[ ! -f "file" ] # NOTcase Statement
# Switch statement
case $COLOR in
red)
echo "Red color"
;;
green)
echo "Green color"
;;
blue|navy)
echo "Blue color"
;;
*)
echo "Unknown color"
;;
esac
# Pattern matching
case $FILENAME in
*.txt)
echo "Text file"
;;
*.pdf)
echo "PDF file"
;;
*)
echo "Unknown type"
;;
esacLoops
for Loop
# Loop through list
for FRUIT in apple banana orange; do
echo "Fruit: $FRUIT"
done
# Loop through array
COLORS=("red" "green" "blue")
for COLOR in "${COLORS[@]}"; do
echo "Color: $COLOR"
done
# Loop with range
for i in {1..5}; do
echo "Number: $i"
done
# Loop with step
for i in {0..10..2}; do
echo $i
done
# C-style loop
for ((i=1; i<=5; i++)); do
echo "Count: $i"
done
# Loop through files
for FILE in /etc/*.conf; do
echo "Config: $FILE"
done
# Loop through command output
for LINE in $(cat file.txt); do
echo "Line: $LINE"
donewhile Loop
# Simple while
COUNT=1
while [ $COUNT -le 5 ]; do
echo "Count: $COUNT"
((COUNT++))
done
# Read file line by line
while read LINE; do
echo "Line: $LINE"
done < file.txt
# Infinite loop
while true; do
echo "Running..."
sleep 1
# Break with: break
done
# Conditional exit
while [ $COUNT -lt 100 ]; do
echo $COUNT
((COUNT+=10))
if [ $COUNT -eq 50 ]; then
break
fi
doneuntil Loop
# Until (opposite of while)
COUNT=1
until [ $COUNT -gt 5 ]; do
echo "Count: $COUNT"
((COUNT++))
done
# Continue to next iteration
for i in {1..5}; do
if [ $i -eq 3 ]; then
continue
fi
echo $i
done
# Break from loop
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo $i
doneFunctions
Creating Functions
# Basic function
greet() {
echo "Hello, World!"
}
# Call function
greet
# Function with arguments
add() {
local A=$1
local B=$2
echo $((A + B))
}
# Call with arguments
add 5 3
# Function with return value
is_even() {
if [ $((${1} % 2)) -eq 0 ]; then
return 0 # Success
else
return 1 # Failure
fi
}
# Check return value
if is_even 4; then
echo "4 is even"
else
echo "4 is odd"
fi
# Function with output
sum() {
echo $((${1} + ${2}))
}
RESULT=$(sum 10 20)
echo "Sum: $RESULT"
# Function with local variables
counter() {
local COUNT=0
COUNT=$((COUNT + 1))
echo $COUNT
}
# Multiple calls (COUNT is local each time)
counter
counter
counterFunction Arguments
# Accessing arguments
print_args() {
echo "Script name: $0"
echo "First arg: $1"
echo "Second arg: $2"
echo "All args: $@"
echo "All args (quoted): $@"
echo "Argument count: $#"
}
# Shift arguments
shift_args() {
echo "First: $1"
shift
echo "Now first: $1"
shift
echo "Now first: $1"
}
# Function that processes all arguments
process_files() {
for FILE in "$@"; do
echo "Processing: $FILE"
done
}
process_files file1 file2 file3Error Handling
Exit Codes
# Check if command succeeded
if grep -q "pattern" file.txt; then
echo "Pattern found"
fi
# Check exit code
command
if [ $? -eq 0 ]; then
echo "Success"
else
echo "Failed"
fi
# Exit with code
exit 0 # Success
exit 1 # Failure
# Return from function
myfunc() {
if [ -z "$1" ]; then
echo "Error: argument required" >&2
return 1
fi
return 0
}
# Set exit on error
set -e # Exit if any command fails
set -u # Error on undefined variable
set -o pipefail # Error if pipe failsError Messages
# Send to stderr
echo "Error: something failed" >&2
# Check for file
if [ ! -f "$FILE" ]; then
echo "Error: file not found" >&2
exit 1
fi
# Validate arguments
if [ $# -lt 2 ]; then
echo "Usage: $0 <arg1> <arg2>" >&2
exit 1
fi
# Trap errors
trap 'echo "Error on line $LINENO"' ERR
# Cleanup on exit
cleanup() {
rm -f /tmp/tempfile
}
trap cleanup EXITPractical Examples
Backup Script
#!/bin/bash
BACKUP_DIR="/backup"
SOURCE_DIR="/home/user/documents"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.tar.gz"
# Create backup
echo "Creating backup..."
tar -czf "$BACKUP_FILE" "$SOURCE_DIR"
if [ $? -eq 0 ]; then
echo "Backup successful: $BACKUP_FILE"
ls -lh "$BACKUP_FILE"
else
echo "Backup failed!" >&2
exit 1
fi
# Clean old backups (older than 30 days)
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +30 -deleteSystem Monitor
#!/bin/bash
# Check CPU usage
CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
echo "CPU Usage: $CPU%"
# Check memory
MEM=$(free | grep Mem | awk '{printf "%.0f", ($3/$2) * 100.0}')
echo "Memory Usage: $MEM%"
# Check disk
DISK=$(df / | awk 'NR==2 {print $5}' | cut -d'%' -f1)
echo "Disk Usage: $DISK%"
# Alert if high
THRESHOLD=80
if [ $CPU -gt $THRESHOLD ]; then
echo "WARNING: High CPU usage!"
fi
if [ $MEM -gt $THRESHOLD ]; then
echo "WARNING: High memory usage!"
fi
if [ $DISK -gt $THRESHOLD ]; then
echo "WARNING: High disk usage!"
fiDeploy Script
#!/bin/bash
set -e # Exit on error
APP_DIR="/opt/myapp"
GIT_REPO="https://github.com/user/repo.git"
BRANCH="${1:-main}"
echo "Deploying branch: $BRANCH"
# Stop application
systemctl stop myapp
# Pull latest code
cd "$APP_DIR"
git fetch origin
git checkout $BRANCH
git pull
# Install dependencies
npm install --production
# Build application
npm run build
# Start application
systemctl start myapp
echo "Deployment complete!"Best Practices
- Use Shebang - Always include #!/bin/bash
- Quote Variables - Use "$VAR" not $VAR
- Check Errors - Test command success with $?
- Use set -e - Exit on errors in scripts
- Comment Code - Explain complex logic
- Use Functions - Don't repeat code
- Validate Input - Check arguments and files
- Use Local Variables - In functions
- Error Messages - Send to stderr (>&2)
- Test Thoroughly - Before production use
Summary
Shell scripting is powerful for automation:
- Variables store and manipulate data
- Conditionals enable decision making
- Loops repeat operations
- Functions organize reusable code
- Error handling ensures reliability
- Practical skills enable DevOps automation
Master shell scripting for efficient Linux administration and automation.