Introduction: Enter the World of Automation with Shell Scripts

Manually handling repetitive tasks wastes time and causes errors. Shell scripts enable you to automate complex tasks and maintain consistent server management. This part covers the basics of Bash shell scripting.

1. Getting Started with Shell Scripts

1.1 Your First Script

#!/bin/bash
# First shell script
# Filename: hello.sh

echo "Hello, World!"
echo "Current time: $(date)"
echo "Current user: $USER"
echo "Current directory: $PWD"

1.2 Running Scripts

# Create script file
vim hello.sh

# Grant execute permission
chmod +x hello.sh

# Execution methods
./hello.sh            # Run from current directory
bash hello.sh         # Run directly with bash
/path/to/hello.sh     # Run with absolute path

# Run without execute permission
bash hello.sh
sh hello.sh

1.3 Understanding Shebang

#!/bin/bash         # Use Bash shell
#!/bin/sh           # Use POSIX compatible shell
#!/usr/bin/env bash # Find bash in environment (portable)
#!/usr/bin/python3  # Python script
#!/usr/bin/perl     # Perl script

2. Variables

2.1 Declaring and Using Variables

#!/bin/bash

# Variable declaration (no spaces around =)
name="John"
age=25
readonly PI=3.14159  # Read-only variable

# Using variables
echo "Name: $name"
echo "Age: ${age} years"  # Curly braces recommended

# Delete variable
unset name

# Default values
echo "${undefined_var:-default}"       # Output default if empty
echo "${undefined_var:=newvalue}"      # Assign newvalue if empty
echo "${name:?Variable not set}"       # Error if empty

2.2 Special Variables

#!/bin/bash
# special_vars.sh arg1 arg2 arg3

echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "All arguments (string): $*"
echo "Argument count: $#"
echo "Current process ID: $$"
echo "Last background process ID: $!"
echo "Last command exit code: $?"

2.3 Environment Variables

#!/bin/bash

# Common environment variables
echo "Home directory: $HOME"
echo "Path: $PATH"
echo "Current shell: $SHELL"
echo "Username: $USER"
echo "Hostname: $HOSTNAME"
echo "Terminal type: $TERM"

# Set environment variable
export MY_VAR="my value"

# View all environment variables
env
printenv

2.4 Arrays

#!/bin/bash

# Array declaration
fruits=("apple" "banana" "orange" "grape")

# Array access
echo "First: ${fruits[0]}"
echo "Third: ${fruits[2]}"
echo "All elements: ${fruits[@]}"
echo "Array length: ${#fruits[@]}"

# Modify array
fruits[1]="mango"
fruits+=("kiwi")  # Add element

# Iterate array
for fruit in "${fruits[@]}"; do
    echo "Fruit: $fruit"
done

# Associative array (Bash 4+)
declare -A person
person[name]="John"
person[age]=25
person[city]="Seoul"

echo "Name: ${person[name]}"
echo "All keys: ${!person[@]}"

3. Operators

3.1 Arithmetic Operations

#!/bin/bash

a=10
b=3

# Arithmetic operation methods
echo "Addition: $((a + b))"
echo "Subtraction: $((a - b))"
echo "Multiplication: $((a * b))"
echo "Division: $((a / b))"
echo "Modulo: $((a % b))"
echo "Exponent: $((a ** 2))"

# let command
let "c = a + b"
echo "let result: $c"

# expr command (spaces required)
result=$(expr $a + $b)
echo "expr result: $result"

# Increment operators
((a++))
echo "After increment: $a"

# Decimal calculation (using bc)
result=$(echo "scale=2; 10 / 3" | bc)
echo "Decimal result: $result"

3.2 String Operations

#!/bin/bash

str="Hello, World!"

# String length
echo "Length: ${#str}"

# Substring
echo "Substring: ${str:0:5}"    # Hello
echo "Last 5 chars: ${str: -5}" # orld!

# String replacement
echo "Replace: ${str/World/Korea}"  # First only
echo "Replace all: ${str//o/O}"     # All o to O

# String removal
filename="document.txt.bak"
echo "Remove extension: ${filename%.*}"       # document.txt
echo "Remove all extensions: ${filename%%.*}" # document
echo "Remove path: ${filename#*/}"
echo "Remove prefix: ${filename##*.}"         # bak

# Case conversion (Bash 4+)
text="Hello World"
echo "Uppercase: ${text^^}"
echo "Lowercase: ${text,,}"

4. Conditionals

4.1 if Statement

#!/bin/bash

age=20

# Basic if
if [ $age -ge 18 ]; then
    echo "Adult"
fi

# if-else
if [ $age -ge 18 ]; then
    echo "Adult"
else
    echo "Minor"
fi

# if-elif-else
if [ $age -lt 13 ]; then
    echo "Child"
elif [ $age -lt 20 ]; then
    echo "Teenager"
else
    echo "Adult"
fi

# Using [[ ]] (more features, Bash specific)
name="John"
if [[ $name == "John" && $age -ge 18 ]]; then
    echo "Adult John"
fi

4.2 Comparison Operators

#!/bin/bash

# Numeric comparison
a=10
b=20

[ $a -eq $b ]  # equal
[ $a -ne $b ]  # not equal
[ $a -lt $b ]  # less than
[ $a -le $b ]  # less or equal
[ $a -gt $b ]  # greater than
[ $a -ge $b ]  # greater or equal

# String comparison
str1="hello"
str2="world"

[ "$str1" = "$str2" ]   # equal
[ "$str1" != "$str2" ]  # not equal
[ -z "$str1" ]          # empty string
[ -n "$str1" ]          # not empty

# String comparison in [[ ]]
[[ $str1 == $str2 ]]    # equal
[[ $str1 < $str2 ]]     # lexicographic compare
[[ $str1 =~ ^h ]]       # regex match

4.3 File Tests

#!/bin/bash

file="/etc/passwd"
dir="/home"

# File existence and type
[ -e $file ]  # exists
[ -f $file ]  # regular file
[ -d $dir ]   # directory
[ -L $file ]  # symbolic link
[ -b $file ]  # block device
[ -c $file ]  # character device
[ -S $file ]  # socket

# File permissions
[ -r $file ]  # readable
[ -w $file ]  # writable
[ -x $file ]  # executable

# File attributes
[ -s $file ]  # size not zero
[ -O $file ]  # owned by current user
[ -G $file ]  # group is current group

# File comparison
[ $file1 -nt $file2 ]  # newer than
[ $file1 -ot $file2 ]  # older than
[ $file1 -ef $file2 ]  # same file (same inode)

# Example
if [ -f "/etc/passwd" ] && [ -r "/etc/passwd" ]; then
    echo "passwd file exists and is readable"
fi

4.4 case Statement

#!/bin/bash

echo "Select a fruit (1-4):"
echo "1) Apple"
echo "2) Banana"
echo "3) Orange"
echo "4) Exit"
read choice

case $choice in
    1)
        echo "You selected Apple"
        ;;
    2)
        echo "You selected Banana"
        ;;
    3)
        echo "You selected Orange"
        ;;
    4)
        echo "Exiting"
        exit 0
        ;;
    *)
        echo "Invalid selection"
        ;;
esac

# Pattern matching example
read -p "Enter filename: " filename
case $filename in
    *.txt)
        echo "Text file"
        ;;
    *.jpg|*.png|*.gif)
        echo "Image file"
        ;;
    *.sh)
        echo "Shell script"
        ;;
    *)
        echo "Unknown file type"
        ;;
esac

5. Loops

5.1 for Loop

#!/bin/bash

# List iteration
for fruit in apple banana orange grape; do
    echo "Fruit: $fruit"
done

# Range iteration
for i in {1..5}; do
    echo "Number: $i"
done

# With step
for i in {0..10..2}; do
    echo "Even: $i"
done

# C-style for loop
for ((i=0; i<5; i++)); do
    echo "Index: $i"
done

# File iteration
for file in /var/log/*.log; do
    echo "Log file: $file"
done

# Command output iteration
for user in $(cat /etc/passwd | cut -d: -f1); do
    echo "User: $user"
done

5.2 while Loop

#!/bin/bash

# Basic while
count=1
while [ $count -le 5 ]; do
    echo "Count: $count"
    ((count++))
done

# Infinite loop
while true; do
    read -p "Enter 'q' to quit: " input
    if [ "$input" = "q" ]; then
        break
    fi
done

# Reading file
while IFS= read -r line; do
    echo "Line: $line"
done < /etc/passwd

# With pipe
cat /etc/passwd | while read line; do
    echo "$line"
done

# Reading multiple fields
while IFS=: read user pass uid gid desc home shell; do
    echo "User: $user, UID: $uid, Home: $home"
done < /etc/passwd

5.3 until Loop

#!/bin/bash

# until: repeat until condition is true
count=1
until [ $count -gt 5 ]; do
    echo "Count: $count"
    ((count++))
done

# Service wait example
until nc -z localhost 80 2>/dev/null; do
    echo "Waiting for web server..."
    sleep 1
done
echo "Web server started!"

5.4 break and continue

#!/bin/bash

# break: exit loop
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        echo "Breaking at 5"
        break
    fi
    echo "Number: $i"
done

# continue: skip to next iteration
for i in {1..10}; do
    if [ $((i % 2)) -eq 0 ]; then
        continue  # Skip even numbers
    fi
    echo "Odd: $i"
done

# break in nested loops
for i in {1..3}; do
    for j in {1..3}; do
        if [ $j -eq 2 ]; then
            break 2  # Break outer loop
        fi
        echo "i=$i, j=$j"
    done
done

6. Functions

6.1 Defining and Calling Functions

#!/bin/bash

# Function definition method 1
function greet {
    echo "Hello!"
}

# Function definition method 2 (recommended)
greet2() {
    echo "Welcome!"
}

# Function call
greet
greet2

6.2 Function Arguments

#!/bin/bash

# Function with arguments
print_info() {
    echo "Name: $1"
    echo "Age: $2"
    echo "All arguments: $@"
    echo "Argument count: $#"
}

print_info "John" 25

# Default value
greet() {
    local name=${1:-"Guest"}
    echo "Hello, ${name}!"
}

greet "John"
greet

6.3 Return Values

#!/bin/bash

# return for exit code (0-255)
is_even() {
    if [ $(($1 % 2)) -eq 0 ]; then
        return 0  # Success/true
    else
        return 1  # Failure/false
    fi
}

if is_even 4; then
    echo "4 is even"
fi

# Return value (using echo)
get_double() {
    echo $(($1 * 2))
}

result=$(get_double 5)
echo "Double of 5: $result"

# Return multiple values
get_stats() {
    local sum=$(($1 + $2))
    local diff=$(($1 - $2))
    echo "$sum $diff"
}

read sum diff <<< $(get_stats 10 3)
echo "Sum: $sum, Diff: $diff"

6.4 Local Variables

#!/bin/bash

global_var="global"

test_scope() {
    local local_var="local"
    global_var="modified in function"

    echo "Local var in function: $local_var"
    echo "Global var in function: $global_var"
}

test_scope

echo "Global var outside: $global_var"
echo "Local var outside: $local_var"  # Empty

7. Input and Output

7.1 User Input

#!/bin/bash

# Basic input
echo "Enter your name:"
read name
echo "Hello, $name!"

# With prompt
read -p "Enter your age: " age

# Password input (hidden)
read -sp "Password: " password
echo ""

# Timeout
read -t 5 -p "Enter within 5 seconds: " input

# Character limit
read -n 1 -p "Continue? (y/n): " answer
echo ""

# Read into array
read -a colors -p "Favorite colors (space separated): "
echo "First color: ${colors[0]}"

7.2 Output

#!/bin/bash

# echo
echo "Normal output"
echo -n "No newline"
echo -e "Tab\tNewline\nSpecial chars"

# printf (formatted)
printf "Name: %s, Age: %d\n" "John" 25
printf "Decimal: %.2f\n" 3.14159
printf "%-10s | %5d\n" "Apple" 100
printf "%-10s | %5d\n" "Banana" 50

# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'  # No Color

echo -e "${RED}Error message${NC}"
echo -e "${GREEN}Success message${NC}"

7.3 Redirection

#!/bin/bash

# Standard output redirection
echo "Log content" > logfile.txt     # Overwrite
echo "Additional" >> logfile.txt     # Append

# Standard error redirection
command 2> error.log                 # Error only
command > output.log 2>&1            # Both output and error
command &> all.log                   # Both (Bash)

# Standard input redirection
while read line; do
    echo "$line"
done < input.txt

# Here Document
cat << EOF
Multiple lines
of text
output at once.
Variable substitution: $USER
EOF

# Here String
grep "pattern" <<< "string to search"

# Pipe
cat /etc/passwd | grep "root" | cut -d: -f1

8. Command Execution and Substitution

8.1 Command Substitution

#!/bin/bash

# Using $() (recommended)
current_date=$(date)
file_count=$(ls -1 | wc -l)

# Using backticks (legacy)
current_date=`date`

# Nesting possible
inner=$(echo "Hello $(whoami)")

echo "Current date: $current_date"
echo "File count: $file_count"

8.2 Checking Command Results

#!/bin/bash

# Check exit code
ls /nonexistent 2>/dev/null
if [ $? -eq 0 ]; then
    echo "Success"
else
    echo "Failed"
fi

# Direct use in conditional
if grep -q "root" /etc/passwd; then
    echo "root user exists"
fi

# && and ||
mkdir test_dir && echo "Directory created"
rm nonexistent 2>/dev/null || echo "File not found"

# Command grouping
{ echo "Line 1"; echo "Line 2"; } > output.txt

9. Practical Script Examples

9.1 System Information Script

#!/bin/bash
# system_info.sh - Display system information

echo "========================================"
echo "        System Information Report"
echo "========================================"
echo ""

echo "Hostname: $(hostname)"
echo "OS: $(uname -o)"
echo "Kernel: $(uname -r)"
echo "Architecture: $(uname -m)"
echo ""

echo "--- CPU Info ---"
echo "CPU: $(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2)"
echo "Cores: $(nproc)"
echo ""

echo "--- Memory Info ---"
free -h | grep -E "Mem|Swap"
echo ""

echo "--- Disk Usage ---"
df -h | grep -E "^/dev"
echo ""

echo "--- Network Info ---"
ip -4 addr show | grep inet | awk '{print $2}'
echo ""

echo "--- System Uptime ---"
uptime
echo ""

echo "========================================"

9.2 Backup Script

#!/bin/bash
# backup.sh - Simple backup script

# Configuration
SOURCE_DIR="/home/$USER/documents"
BACKUP_DIR="/home/$USER/backups"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Execute backup
echo "Starting backup: $SOURCE_DIR"
if tar -czf "${BACKUP_DIR}/${BACKUP_FILE}" -C "$(dirname $SOURCE_DIR)" "$(basename $SOURCE_DIR)" 2>/dev/null; then
    echo "Backup complete: ${BACKUP_DIR}/${BACKUP_FILE}"
    echo "File size: $(du -h ${BACKUP_DIR}/${BACKUP_FILE} | cut -f1)"
else
    echo "Backup failed!"
    exit 1
fi

# Delete old backups (older than 7 days)
echo ""
echo "Cleaning old backups..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "Done!"

9.3 Log Monitoring Script

#!/bin/bash
# log_monitor.sh - Monitor log files

LOG_FILE="${1:-/var/log/syslog}"
KEYWORD="${2:-error}"

if [ ! -f "$LOG_FILE" ]; then
    echo "File does not exist: $LOG_FILE"
    exit 1
fi

echo "Monitoring log: $LOG_FILE"
echo "Keyword: $KEYWORD"
echo "Exit: Ctrl+C"
echo "----------------------------"

tail -f "$LOG_FILE" | while read line; do
    if echo "$line" | grep -qi "$KEYWORD"; then
        echo "[$(date '+%H:%M:%S')] $line"
    fi
done

10. Debugging

10.1 Debug Mode

#!/bin/bash

# Debug entire script
bash -x script.sh

# Enable debug within script
set -x  # Start debug
# commands...
set +x  # End debug

# Debug specific section
debug() {
    if [ "$DEBUG" = "1" ]; then
        echo "[DEBUG] $*" >&2
    fi
}

DEBUG=1
debug "This is a debug message"

10.2 Error Handling

#!/bin/bash

# Exit immediately on error
set -e

# Error on undefined variable
set -u

# Detect pipe errors
set -o pipefail

# Set all
set -euo pipefail

# Error handler
trap 'echo "Error occurred: line $LINENO"; exit 1' ERR

# Cleanup on exit
cleanup() {
    echo "Performing cleanup..."
    rm -f /tmp/tempfile
}
trap cleanup EXIT

Conclusion

You've learned the basics of shell scripting. By combining variables, conditionals, loops, and functions, you can create various automation scripts. The next part covers advanced shell scripting techniques including regular expressions, text processing, and advanced patterns.