Loading...

Shell Scripting Basics: Bash Variables, Conditionals, Loops, Functions, and Error Handling

Back to blog
LinuxBashScriptingDevOps

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.

10 min read

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.sh

Comments

# This is a comment
# Comments are ignored by interpreter
 
echo "This runs"  # Inline comment

Variables

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 expanded

Variable 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.txt

Output

# 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>&1

Conditionals

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"
fi

Test 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" ]           # NOT

case 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"
    ;;
esac

Loops

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"
done

while 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
done

until 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
done

Functions

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
counter

Function 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 file3

Error 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 fails

Error 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 EXIT

Practical 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 -delete

System 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!"
fi

Deploy 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

  1. Use Shebang - Always include #!/bin/bash
  2. Quote Variables - Use "$VAR" not $VAR
  3. Check Errors - Test command success with $?
  4. Use set -e - Exit on errors in scripts
  5. Comment Code - Explain complex logic
  6. Use Functions - Don't repeat code
  7. Validate Input - Check arguments and files
  8. Use Local Variables - In functions
  9. Error Messages - Send to stderr (>&2)
  10. 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.