Linux Shell Scripting¶
Overview¶
Shell scripting is essential for automating repetitive tasks, system administration, and DevOps workflows. This guide covers Bash scripting fundamentals with practical examples.
Getting Started¶
Creating a Script¶
Basic Script Structure¶
Shebang (#!/bin/bash):
- Must be first line
- Tells system which interpreter to use
- Common options:
- #!/bin/bash - Bash shell
- #!/bin/sh - POSIX shell
- #!/usr/bin/env bash - Portable (finds bash in PATH)
Variables¶
Declaring Variables¶
Using variables¶
echo "Name: $name" echo "Age: ${age}"
Command substitution¶
current_date=$(date) user_count=$(who | wc -l)
echo "Date: $current_date" echo "Users logged in: $user_count"
### Special Variables
```bash
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
echo "Exit status of last command: $?"
echo "Process ID: $$"
Example usage:
./script.sh arg1 arg2 arg3
# $0 = ./script.sh
# $1 = arg1
# $2 = arg2
# $@ = arg1 arg2 arg3
# $# = 3
Environment Variables¶
#!/bin/bash
# Common environment variables
echo "Home directory: $HOME"
echo "Current user: $USER"
echo "Current path: $PWD"
echo "Shell: $SHELL"
echo "PATH: $PATH"
# Set environment variable
export MY_VAR="value"
Input and Output¶
Reading User Input¶
#!/bin/bash
# Simple input
echo "Enter your name:"
read name
echo "Hello, $name!"
# Input with prompt
read -p "Enter your age: " age
echo "You are $age years old"
# Silent input (for passwords)
read -sp "Enter password: " password
echo ""
echo "Password saved"
# Read multiple values
read -p "Enter first and last name: " first last
echo "First: $first, Last: $last"
Output¶
#!/bin/bash
# Standard output
echo "This goes to stdout"
printf "Formatted: %s %d\n" "Number" 42
# Error output
echo "Error message" >&2
# Redirect output
echo "Save to file" > output.txt
echo "Append to file" >> output.txt
# Suppress output
command > /dev/null 2>&1
Conditionals¶
If Statements¶
#!/bin/bash
# Basic if
if [ "$USER" = "root" ]; then
echo "Running as root"
fi
# If-else
if [ -f "file.txt" ]; then
echo "File exists"
else
echo "File does not exist"
fi
# If-elif-else
if [ $age -lt 18 ]; then
echo "Minor"
elif [ $age -lt 65 ]; then
echo "Adult"
else
echo "Senior"
fi
Test Operators¶
File tests:
[ -f file ] # File exists and is regular file
[ -d dir ] # Directory exists
[ -e path ] # Path exists (file or directory)
[ -r file ] # File is readable
[ -w file ] # File is writable
[ -x file ] # File is executable
[ -s file ] # File exists and is not empty
[ -L link ] # Path is symbolic link
String tests:
[ -z "$str" ] # String is empty
[ -n "$str" ] # String is not empty
[ "$a" = "$b" ] # Strings are equal
[ "$a" != "$b" ] # Strings are not equal
Numeric tests:
[ $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
Logical operators:
Case Statements¶
#!/bin/bash
read -p "Enter a choice (start/stop/restart): " choice
case $choice in
start)
echo "Starting service..."
;;
stop)
echo "Stopping service..."
;;
restart)
echo "Restarting service..."
;;
*)
echo "Invalid choice"
;;
esac
Loops¶
For Loop¶
#!/bin/bash
# Loop over list
for item in apple banana cherry; do
echo "Fruit: $item"
done
# Loop over files
for file in *.txt; do
echo "Processing $file"
done
# C-style for loop
for ((i=1; i<=5; i++)); do
echo "Number: $i"
done
# Loop over command output
for user in $(cat /etc/passwd | cut -d: -f1); do
echo "User: $user"
done
While Loop¶
#!/bin/bash
# Basic while loop
counter=1
while [ $counter -le 5 ]; do
echo "Count: $counter"
((counter++))
done
# Read file line by line
while read line; do
echo "Line: $line"
done < file.txt
# Infinite loop
while true; do
echo "Press Ctrl+C to stop"
sleep 1
done
Until Loop¶
#!/bin/bash
# Run until condition is true
counter=1
until [ $counter -gt 5 ]; do
echo "Count: $counter"
((counter++))
done
Loop Control¶
#!/bin/bash
# Break - exit loop
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo $i
done
# Continue - skip iteration
for i in {1..10}; do
if [ $i -eq 5 ]; then
continue
fi
echo $i
done
Functions¶
Defining Functions¶
#!/bin/bash
# Method 1
function greet() {
echo "Hello, $1!"
}
# Method 2
say_goodbye() {
echo "Goodbye, $1!"
}
# Call functions
greet "Alice"
say_goodbye "Bob"
Function with Return Value¶
#!/bin/bash
add_numbers() {
local sum=$(($1 + $2))
echo $sum
}
result=$(add_numbers 5 3)
echo "Result: $result"
Function with Return Status¶
#!/bin/bash
check_file() {
if [ -f "$1" ]; then
return 0 # Success
else
return 1 # Failure
fi
}
if check_file "test.txt"; then
echo "File exists"
else
echo "File not found"
fi
Local Variables¶
#!/bin/bash
my_function() {
local local_var="I'm local"
global_var="I'm global"
echo $local_var
}
my_function
echo $global_var # Accessible
echo $local_var # Not accessible (empty)
Arrays¶
Indexed Arrays¶
#!/bin/bash
# Declare array
fruits=("apple" "banana" "cherry")
# Access elements
echo ${fruits[0]} # First element
echo ${fruits[1]} # Second element
echo ${fruits[@]} # All elements
echo ${#fruits[@]} # Array length
# Add element
fruits+=("date")
# Loop through array
for fruit in "${fruits[@]}"; do
echo $fruit
done
# Loop with index
for i in "${!fruits[@]}"; do
echo "Index $i: ${fruits[$i]}"
done
Associative Arrays (Bash 4+)¶
#!/bin/bash
# Declare associative array
declare -A person
person[name]="John"
person[age]=30
person[city]="New York"
# Access elements
echo ${person[name]}
echo ${person[age]}
# Loop through keys
for key in "${!person[@]}"; do
echo "$key: ${person[$key]}"
done
String Manipulation¶
String Operations¶
#!/bin/bash
text="Hello World"
# Length
echo ${#text} # 11
# Substring
echo ${text:0:5} # Hello
echo ${text:6} # World
# Replace
echo ${text/World/Universe} # Hello Universe
echo ${text//o/0} # Hell0 W0rld (replace all)
# Remove prefix/suffix
filename="script.sh"
echo ${filename%.sh} # script (remove .sh)
echo ${filename#script.} # sh (remove script.)
# Uppercase/lowercase
echo ${text^^} # HELLO WORLD
echo ${text,,} # hello world
String Concatenation¶
#!/bin/bash
first="Hello"
second="World"
# Method 1
result="$first $second"
# Method 2
result="${first} ${second}"
echo $result
Arithmetic Operations¶
Basic Arithmetic¶
#!/bin/bash
# Using $(( ))
a=10
b=5
echo $((a + b)) # 15
echo $((a - b)) # 5
echo $((a * b)) # 50
echo $((a / b)) # 2
echo $((a % b)) # 0
# Increment/decrement
((a++))
((b--))
((a += 5))
# Using let
let result=a+b
echo $result
# Using expr (older method)
result=$(expr $a + $b)
echo $result
Floating Point Arithmetic¶
#!/bin/bash
# Bash doesn't support floating point natively
# Use bc calculator
result=$(echo "scale=2; 10 / 3" | bc)
echo $result # 3.33
result=$(echo "scale=4; 22 / 7" | bc)
echo $result # 3.1428
Practical Script Examples¶
1. Backup Script¶
#!/bin/bash
# Backup important files
SOURCE="/home/user/documents"
DEST="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"
echo "Starting backup..."
tar -czf "${DEST}/${BACKUP_FILE}" "$SOURCE"
if [ $? -eq 0 ]; then
echo "Backup completed: ${BACKUP_FILE}"
else
echo "Backup failed!" >&2
exit 1
fi
2. System Health Check¶
#!/bin/bash
echo "=== System Health Check ==="
echo "Date: $(date)"
echo ""
# CPU Load
echo "CPU Load:"
uptime
# Memory Usage
echo ""
echo "Memory Usage:"
free -h
# Disk Usage
echo ""
echo "Disk Usage:"
df -h | grep -v tmpfs
# Check if disk usage > 80%
df -h | awk '{print $5 " " $6}' | while read usage mount; do
usage_num=${usage%\%}
if [ "$usage_num" -gt 80 ] 2>/dev/null; then
echo "WARNING: $mount is ${usage} full"
fi
done
3. Log Rotation Script¶
#!/bin/bash
LOG_DIR="/var/log/myapp"
MAX_DAYS=30
echo "Rotating logs older than $MAX_DAYS days..."
find "$LOG_DIR" -name "*.log" -type f -mtime +$MAX_DAYS -exec gzip {} \;
find "$LOG_DIR" -name "*.log.gz" -type f -mtime +90 -delete
echo "Log rotation completed"
4. Service Monitor¶
#!/bin/bash
SERVICE="nginx"
if systemctl is-active --quiet "$SERVICE"; then
echo "$SERVICE is running"
else
echo "$SERVICE is not running. Starting..."
systemctl start "$SERVICE"
if systemctl is-active --quiet "$SERVICE"; then
echo "$SERVICE started successfully"
else
echo "Failed to start $SERVICE" >&2
exit 1
fi
fi
5. User Creation Script¶
#!/bin/bash
if [ $# -ne 1 ]; then
echo "Usage: $0 <username>"
exit 1
fi
USERNAME=$1
# Check if user exists
if id "$USERNAME" &>/dev/null; then
echo "User $USERNAME already exists"
exit 1
fi
# Create user
useradd -m -s /bin/bash "$USERNAME"
if [ $? -eq 0 ]; then
echo "User $USERNAME created successfully"
passwd "$USERNAME"
else
echo "Failed to create user" >&2
exit 1
fi
6. File Organizer¶
#!/bin/bash
SOURCE_DIR="$HOME/Downloads"
DEST_DIR="$HOME/Organized"
# Create destination directories
mkdir -p "$DEST_DIR"/{Images,Documents,Videos,Archives,Others}
# Move files based on extension
for file in "$SOURCE_DIR"/*; do
if [ -f "$file" ]; then
case "${file##*.}" in
jpg|jpeg|png|gif)
mv "$file" "$DEST_DIR/Images/"
;;
pdf|doc|docx|txt)
mv "$file" "$DEST_DIR/Documents/"
;;
mp4|avi|mkv)
mv "$file" "$DEST_DIR/Videos/"
;;
zip|tar|gz|rar)
mv "$file" "$DEST_DIR/Archives/"
;;
*)
mv "$file" "$DEST_DIR/Others/"
;;
esac
fi
done
echo "Files organized successfully"
Error Handling¶
Exit Status¶
#!/bin/bash
# Check command success
if command; then
echo "Success"
else
echo "Failed"
fi
# Check exit status
command
if [ $? -eq 0 ]; then
echo "Success"
fi
Set Options¶
#!/bin/bash
set -e # Exit on error
set -u # Exit on undefined variable
set -o pipefail # Exit on pipe failure
# Combine options
set -euo pipefail
Trap Errors¶
#!/bin/bash
# Cleanup on exit
cleanup() {
echo "Cleaning up..."
rm -f /tmp/tempfile
}
trap cleanup EXIT
# Catch errors
error_handler() {
echo "Error on line $1"
exit 1
}
trap 'error_handler $LINENO' ERR
Debugging¶
Debug Options¶
#!/bin/bash
set -x # Print commands before execution
set -v # Print script lines as read
# Run script in debug mode
bash -x script.sh
Debug Output¶
#!/bin/bash
DEBUG=true
debug() {
if [ "$DEBUG" = true ]; then
echo "[DEBUG] $@" >&2
fi
}
debug "This is a debug message"
echo "Normal output"
Best Practices¶
- Always use shebang -
#!/bin/bashat the top - Quote variables - Use
"$var"to prevent word splitting - Check arguments - Validate input before processing
- Use meaningful names - Clear variable and function names
- Add comments - Explain complex logic
- Handle errors - Check exit status and use
set -e - Use functions - Break complex scripts into functions
- Test thoroughly - Test with various inputs and edge cases
- Make scripts portable - Avoid bash-specific features if possible
- Use shellcheck - Lint your scripts with shellcheck tool
Related Topics¶
- Basics and Navigation - File operations
- Process Management - Managing processes
- System Monitoring - Monitoring automation
- File Management - File operations