Shell Scripting
1. Shell Script Basics
A shell script is a program that collects commands in a file for automation.
First Script
#!/bin/bash
# This is a comment
echo "Hello, World!"
shebang (#!)
The first line of a script specifies the interpreter.
#!/bin/bash # bash shell
#!/bin/sh # standard shell
#!/usr/bin/env bash # find bash in environment (more portable)
Running Scripts
# 1. Grant execute permission and run
chmod +x script.sh
./script.sh
# 2. Run directly with bash
bash script.sh
# 3. Run with source (in current shell)
source script.sh
. script.sh
2. Variables
Variable Declaration and Usage
#!/bin/bash
# Variable declaration (no spaces around =!)
name="John"
age=25
readonly PI=3.14159 # constant
# Variable usage
echo $name
echo ${name} # recommended (clearer)
echo "Hello, ${name}!"
echo "Age: $age"
# Delete variable
unset name
Special Variables
| Variable |
Description |
$0 |
Script name |
$1` ~ `$9 |
Positional parameters |
$# |
Number of parameters |
$@ |
All parameters (individual) |
$* |
All parameters (string) |
$? |
Exit status of last command |
$$ |
Current process PID |
$! |
Last background PID |
#!/bin/bash
echo "Script: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Argument count: $#"
echo "All arguments: $@"
echo "PID: $$"
Environment Variables
# View environment variables
env
printenv
# Key environment variables
echo $HOME # Home directory
echo $USER # Username
echo $PATH # Execution path
echo $PWD # Current directory
echo $SHELL # Current shell
# Set environment variable
export MY_VAR="value"
# Set within script
#!/bin/bash
export PATH="$PATH:/opt/myapp/bin"
Variable Default Values
# Use default value if variable is unset
name=${name:-"default"}
# Assign default value if variable is unset
name=${name:="default"}
# Error if variable is unset
name=${name:?"Variable not set"}
read Command
#!/bin/bash
# Basic input
echo -n "Enter your name: "
read name
echo "Hello, $name!"
# With prompt
read -p "Enter your age: " age
echo "You are ${age} years old."
# Password input (hidden)
read -sp "Password: " password
echo
echo "Password set."
# Timeout
read -t 5 -p "Enter within 5 seconds: " input
# Multiple variables
read -p "Name and age: " name age
echo "$name, $age"
Command-line Arguments
#!/bin/bash
# Usage: ./script.sh arg1 arg2
if [ $# -lt 2 ]; then
echo "Usage: $0 <name> <age>"
exit 1
fi
name=$1
age=$2
echo "$name is ${age} years old."
4. Conditionals
if Statement
#!/bin/bash
# Basic format
if [ condition ]; then
command
fi
# if-else
if [ condition ]; then
command1
else
command2
fi
# if-elif-else
if [ condition1 ]; then
command1
elif [ condition2 ]; then
command2
else
command3
fi
Comparison Operators
Numeric Comparison
| Operator |
Description |
-eq |
Equal |
-ne |
Not equal |
-gt |
Greater than |
-ge |
Greater or equal |
-lt |
Less than |
-le |
Less or equal |
#!/bin/bash
num=10
if [ $num -gt 5 ]; then
echo "$num is greater than 5."
fi
if [ $num -eq 10 ]; then
echo "$num is 10."
fi
String Comparison
| Operator |
Description |
= |
Equal |
!= |
Not equal |
-z |
Empty string |
-n |
Not empty |
#!/bin/bash
str="hello"
if [ "$str" = "hello" ]; then
echo "String is hello."
fi
if [ -z "$empty" ]; then
echo "Variable is empty."
fi
if [ -n "$str" ]; then
echo "Variable has value."
fi
File Tests
| Operator |
Description |
-e |
File exists |
-f |
Regular file |
-d |
Directory |
-r |
Readable |
-w |
Writable |
-x |
Executable |
-s |
File size > 0 |
#!/bin/bash
file="/etc/passwd"
if [ -e "$file" ]; then
echo "File exists."
fi
if [ -f "$file" ]; then
echo "Regular file."
fi
if [ -d "/home" ]; then
echo "Directory."
fi
if [ -r "$file" ]; then
echo "Readable."
fi
Logical Operators
#!/bin/bash
# AND
if [ $a -gt 0 ] && [ $b -gt 0 ]; then
echo "Both positive"
fi
# OR
if [ $a -gt 0 ] || [ $b -gt 0 ]; then
echo "At least one positive"
fi
# NOT
if [ ! -f "file.txt" ]; then
echo "File doesn't exist."
fi
# Using [[ ]] (recommended)
if [[ $a -gt 0 && $b -gt 0 ]]; then
echo "Both positive"
fi
case Statement
#!/bin/bash
read -p "Choose a fruit (apple/banana/orange): " fruit
case $fruit in
apple)
echo "You chose apple."
;;
banana)
echo "You chose banana."
;;
orange)
echo "You chose orange."
;;
*)
echo "Unknown fruit."
;;
esac
5. Loops
for Loop
#!/bin/bash
# List iteration
for name in Alice Bob Charlie; do
echo "Hello, $name!"
done
# Range iteration
for i in {1..5}; do
echo "Number: $i"
done
# With increment
for i in {0..10..2}; do
echo "Even: $i"
done
# C-style
for ((i=0; i<5; i++)); do
echo "Index: $i"
done
# File list
for file in *.txt; do
echo "Processing: $file"
done
# Command output
for user in $(cat /etc/passwd | cut -d: -f1); do
echo "User: $user"
done
while Loop
#!/bin/bash
# Basic while
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
((count++))
done
# Read file
while read line; do
echo "Line: $line"
done < file.txt
# Infinite loop
while true; do
echo "Running... (Ctrl+C to exit)"
sleep 1
done
until Loop
#!/bin/bash
# Repeat until condition is true
count=1
until [ $count -gt 5 ]; do
echo "Count: $count"
((count++))
done
break and continue
#!/bin/bash
# break - exit loop
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo $i
done
# continue - skip to next iteration
for i in {1..5}; do
if [ $i -eq 3 ]; then
continue
fi
echo $i
done
6. Functions
Function Definition
#!/bin/bash
# Method 1
function greet() {
echo "Hello, $1!"
}
# Method 2
say_bye() {
echo "Goodbye, $1!"
}
# Call
greet "World"
say_bye "World"
Function Parameters
#!/bin/bash
print_info() {
echo "Name: $1"
echo "Age: $2"
echo "Argument count: $#"
}
print_info "John" 25
Return Values
#!/bin/bash
# return for exit status (0-255)
is_even() {
if [ $(($1 % 2)) -eq 0 ]; then
return 0 # true
else
return 1 # false
fi
}
if is_even 4; then
echo "Even number."
fi
# echo for value return
add() {
echo $(($1 + $2))
}
result=$(add 5 3)
echo "5 + 3 = $result"
Local Variables
#!/bin/bash
my_func() {
local local_var="I'm local"
global_var="I'm global"
echo "$local_var"
}
my_func
echo "$global_var" # prints
echo "$local_var" # doesn't print
7. Arrays
#!/bin/bash
# Array declaration
fruits=("apple" "banana" "orange")
# Access by index
echo ${fruits[0]} # apple
echo ${fruits[1]} # banana
# All elements
echo ${fruits[@]}
# Element count
echo ${#fruits[@]}
# Add element
fruits+=("grape")
# Iterate array
for fruit in "${fruits[@]}"; do
echo $fruit
done
# With index
for i in "${!fruits[@]}"; do
echo "$i: ${fruits[$i]}"
done
8. Practical Script Examples
Backup Script
#!/bin/bash
# backup.sh - Directory backup script
SOURCE_DIR="${1:-/var/www}"
BACKUP_DIR="${2:-/backup}"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"
# Check backup directory
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
fi
# Check source directory
if [ ! -d "$SOURCE_DIR" ]; then
echo "Error: $SOURCE_DIR directory not found."
exit 1
fi
# Execute backup
echo "Starting backup: $SOURCE_DIR -> $BACKUP_DIR/$BACKUP_FILE"
tar -czvf "$BACKUP_DIR/$BACKUP_FILE" -C "$(dirname $SOURCE_DIR)" "$(basename $SOURCE_DIR)"
if [ $? -eq 0 ]; then
echo "Backup complete: $BACKUP_DIR/$BACKUP_FILE"
# Delete backups older than 30 days
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +30 -delete
echo "Old backups cleaned"
else
echo "Backup failed!"
exit 1
fi
Server Health Check Script
#!/bin/bash
# health_check.sh - Server health check
LOG_FILE="/var/log/health_check.log"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
check_disk() {
local usage=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$usage" -gt 80 ]; then
log_message "Warning: Disk usage ${usage}%"
return 1
fi
log_message "Disk: ${usage}% used"
return 0
}
check_memory() {
local usage=$(free | awk '/Mem:/ {printf("%.0f", $3/$2 * 100)}')
if [ "$usage" -gt 80 ]; then
log_message "Warning: Memory usage ${usage}%"
return 1
fi
log_message "Memory: ${usage}% used"
return 0
}
check_service() {
local service=$1
if systemctl is-active --quiet "$service"; then
log_message "Service $service: running"
return 0
else
log_message "Warning: Service $service stopped"
return 1
fi
}
# Main
log_message "===== Health check started ====="
check_disk
check_memory
check_service "sshd"
check_service "nginx" 2>/dev/null || true
log_message "===== Health check completed ====="
Log Analysis Script
#!/bin/bash
# log_analyzer.sh - Log analysis
LOG_FILE="${1:-/var/log/syslog}"
if [ ! -f "$LOG_FILE" ]; then
echo "File not found: $LOG_FILE"
exit 1
fi
echo "=== Log Analysis: $LOG_FILE ==="
echo
echo "=== Error Count ==="
grep -ci "error" "$LOG_FILE"
echo
echo "=== Recent 10 Errors ==="
grep -i "error" "$LOG_FILE" | tail -10
echo
echo "=== Log Count by Hour ==="
awk '{print $3}' "$LOG_FILE" | cut -d: -f1 | sort | uniq -c | sort -k2n
User Creation Script
#!/bin/bash
# create_users.sh - Batch user creation from file
# Usage: sudo ./create_users.sh users.txt
# users.txt format: username:password:groupname
USER_FILE="$1"
if [ -z "$USER_FILE" ] || [ ! -f "$USER_FILE" ]; then
echo "Usage: $0 <userfile>"
exit 1
fi
while IFS=: read -r username password groupname; do
# Skip empty lines or comments
[[ -z "$username" || "$username" =~ ^# ]] && continue
# Check if user exists
if id "$username" &>/dev/null; then
echo "User exists: $username (skipped)"
continue
fi
# Create group if it doesn't exist
if ! getent group "$groupname" &>/dev/null; then
groupadd "$groupname"
echo "Group created: $groupname"
fi
# Create user
useradd -m -s /bin/bash -g "$groupname" "$username"
echo "$username:$password" | chpasswd
echo "User created: $username (group: $groupname)"
done < "$USER_FILE"
echo "Done!"
9. Debugging
Debug Mode
# Trace script execution
bash -x script.sh
# Enable within script
#!/bin/bash
set -x # Start debug
# commands...
set +x # Stop debug
# Exit on error
set -e
# Error on undefined variable
set -u
# Detect pipe errors
set -o pipefail
# Best practice (script start)
#!/bin/bash
set -euo pipefail
10. Practice Exercises
Exercise 1: Simple Calculator
#!/bin/bash
# calculator.sh
read -p "First number: " num1
read -p "Operator (+, -, *, /): " op
read -p "Second number: " num2
case $op in
+) result=$((num1 + num2)) ;;
-) result=$((num1 - num2)) ;;
\*) result=$((num1 * num2)) ;;
/) result=$((num1 / num2)) ;;
*) echo "Invalid operator"; exit 1 ;;
esac
echo "Result: $num1 $op $num2 = $result"
Exercise 2: File Organization Script
#!/bin/bash
# organize.sh - Organize files by extension
DIR="${1:-.}"
for file in "$DIR"/*; do
[ -f "$file" ] || continue
ext="${file##*.}"
mkdir -p "$DIR/$ext"
mv "$file" "$DIR/$ext/"
echo "Moved: $file -> $DIR/$ext/"
done
Next Steps
Let's learn about network management in 10_Network_Basics.md!