跳转到内容

Shell脚本进阶

1. Shell脚本基础回顾

1.1 Shell脚本的概念

Shell脚本是一种用于自动化执行命令的脚本文件,它使用Shell语言编写,可以在命令行环境中执行。Shell脚本可以:

  • 自动化执行一系列命令
  • 处理文本数据
  • 控制系统服务
  • 进行系统管理任务
  • 实现复杂的业务逻辑

1.2 常见的Shell类型

Shell类型描述特点默认系统
bashBourne Again Shell功能丰富,支持数组、函数等高级特性Linux默认
shBourne Shell基础Shell,兼容性好早期UNIX
cshC Shell语法类似C语言某些UNIX系统
tcshTurbo C Shellcsh的增强版本某些UNIX系统
kshKorn Shell结合了bash和csh的优点某些UNIX系统
zshZ Shell功能强大,支持主题和插件可自定义安装

1.3 Shell脚本的基本结构

bash
#!/bin/bash
# 脚本注释

# 变量定义
VARIABLE="value"

# 函数定义
function_name() {
    echo "Function body"
}

# 主逻辑
if [ condition ]; then
    echo "True"
else
    echo "False"
fi

# 调用函数
function_name

2. Shell脚本高级语法

2.1 变量高级用法

2.1.1 变量替换

bash
# 基本变量替换
name="John"
echo "Hello, $name"

# 变量替换与命令替换
echo "Current directory: $(pwd)"
echo "Current directory: `pwd`"

# 变量替换与字符串操作
string="Hello, World"
echo "Length: ${#string}"         # 字符串长度
echo "Substring: ${string:7:5}"     # 从位置7开始,取5个字符
echo "Replace: ${string/World/Shell}" # 替换字符串

echo "Remove prefix: ${string#Hello, }" # 移除前缀
echo "Remove suffix: ${string%, World}" # 移除后缀

# 变量默认值
unset var
echo "${var:-default}"  # 如果var未设置,使用default
echo "${var:=default}"  # 如果var未设置,设置为default并使用
echo "${var:+value}"    # 如果var已设置,使用value
echo "${var:?error}"    # 如果var未设置,输出error并退出

2.1.2 数组

bash
# 数组定义
array=(
    "element1"
    "element2"
    "element3"
)

# 访问数组元素
echo "First element: ${array[0]}"
echo "Second element: ${array[1]}"
echo "All elements: ${array[@]}"
echo "Number of elements: ${#array[@]}"

# 数组操作
array[3]="element4"      # 添加元素
unset array[1]           # 删除元素
array=(${array[@]})      # 重建数组(压缩索引)

# 遍历数组
for element in "${array[@]}"; do
    echo "Element: $element"
done

# 关联数组(bash 4+)
declare -A assoc_array
assoc_array["name"]="John"
assoc_array["age"]="30"
assoc_array["city"]="New York"

# 访问关联数组
echo "Name: ${assoc_array[name]}"
echo "All keys: ${!assoc_array[@]}"
echo "All values: ${assoc_array[@]}"

# 遍历关联数组
for key in "${!assoc_array[@]}"; do
    echo "$key: ${assoc_array[$key]}"
done

2.2 函数高级用法

2.2.1 函数定义与调用

bash
# 函数定义方式 1
function hello {
    echo "Hello, $1"
}

# 函数定义方式 2
hello() {
    echo "Hello, $1"
}

# 函数调用
hello "World"

# 带返回值的函数
sum() {
    local a=$1
    local b=$2
    return $((a + b))
}

sum 5 3
result=$?
echo "Sum: $result"

# 返回字符串的函数
get_greeting() {
    local name=$1
    echo "Hello, $name"
}

greeting=$(get_greeting "John")
echo "Greeting: $greeting"

2.2.2 函数参数处理

bash
# 处理任意数量的参数
print_args() {
    echo "Number of arguments: $#"
    echo "All arguments: $@"
    echo "First argument: $1"
    echo "Second argument: $2"
}

print_args 1 2 3 4 5

# 移动参数
shift_args() {
    echo "Initial arguments: $@"
    shift 2  # 移除前2个参数
    echo "After shift: $@"
}

shift_args a b c d e

# 遍历所有参数
for_each() {
    for arg in "$@"; do
        echo "Argument: $arg"
    done
}

for_each apple banana cherry

2.2.3 函数作用域

bash
# 全局变量
GLOBAL_VAR="global"

# 函数中的局部变量
scope_demo() {
    local LOCAL_VAR="local"
    GLOBAL_VAR="modified global"
    echo "Inside function - LOCAL_VAR: $LOCAL_VAR"
    echo "Inside function - GLOBAL_VAR: $GLOBAL_VAR"
}

# 调用函数
scope_demo

# 外部访问
echo "Outside function - GLOBAL_VAR: $GLOBAL_VAR"
echo "Outside function - LOCAL_VAR: $LOCAL_VAR"  # 未定义

2.3 控制结构高级用法

2.3.1 条件判断高级用法

bash
# 文件测试
test_file() {
    local file=$1
    
    if [ -f "$file" ]; then
        echo "$file is a regular file"
    elif [ -d "$file" ]; then
        echo "$file is a directory"
    elif [ -L "$file" ]; then
        echo "$file is a symlink"
    elif [ -e "$file" ]; then
        echo "$file exists"
    else
        echo "$file does not exist"
    fi
}

test_file "/etc/passwd"
test_file "/etc"
test_file "/etc/hosts"
test_file "/nonexistent"

# 字符串测试
string_test() {
    local str1=$1
    local str2=$2
    
    if [ -z "$str1" ]; then
        echo "str1 is empty"
    elif [ -n "$str1" ]; then
        echo "str1 is not empty"
    fi
    
    if [ "$str1" = "$str2" ]; then
        echo "str1 equals str2"
    elif [ "$str1" != "$str2" ]; then
        echo "str1 does not equal str2"
    fi
    
    if [ "$str1" \< "$str2" ]; then
        echo "str1 is less than str2"
    elif [ "$str1" \> "$str2" ]; then
        echo "str1 is greater than str2"
    fi
}

string_test "apple" "banana"

# 数值测试
number_test() {
    local num1=$1
    local num2=$2
    
    if [ $num1 -eq $num2 ]; then
        echo "num1 equals num2"
    elif [ $num1 -ne $num2 ]; then
        echo "num1 does not equal num2"
    fi
    
    if [ $num1 -lt $num2 ]; then
        echo "num1 is less than num2"
    elif [ $num1 -le $num2 ]; then
        echo "num1 is less than or equal to num2"
    elif [ $num1 -gt $num2 ]; then
        echo "num1 is greater than num2"
    elif [ $num1 -ge $num2 ]; then
        echo "num1 is greater than or equal to num2"
    fi
}

number_test 5 10

# 逻辑运算
logic_test() {
    local a=$1
    local b=$2
    
    if [ $a -gt 0 ] && [ $b -gt 0 ]; then
        echo "Both numbers are positive"
    fi
    
    if [ $a -gt 0 ] || [ $b -gt 0 ]; then
        echo "At least one number is positive"
    fi
    
    if ! [ $a -eq 0 ]; then
        echo "a is not zero"
    fi
}

logic_test 5 -3

# 高级条件表达式(bash)
advanced_cond() {
    local num=$1
    
    if [[ $num =~ ^[0-9]+$ ]]; then
        echo "$num is a number"
    else
        echo "$num is not a number"
    fi
    
    local str=$2
    if [[ $str == *"test"* ]]; then
        echo "$str contains 'test'"
    else
        echo "$str does not contain 'test'"
    fi
}

advanced_cond 42 "testing"

2.3.2 循环高级用法

bash
# for 循环遍历文件
for file in *.txt; do
    if [ -f "$file" ]; then
        echo "Processing $file"
    fi
done

# for 循环遍历数字
for i in {1..10}; do
    echo "Number: $i"
done

# for 循环自定义步长
for i in {1..10..2}; do
    echo "Odd number: $i"
done

# while 循环读取文件
while IFS= read -r line; do
    echo "Line: $line"
done < "file.txt"

# while 循环条件判断
count=1
while [ $count -le 5 ]; do
    echo "Count: $count"
    ((count++))
done

# until 循环
count=1
until [ $count -gt 5 ]; do
    echo "Count: $count"
    ((count++))
done

# 循环控制
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        echo "Breaking at $i"
        break
    fi
    if [ $i -eq 3 ]; then
        echo "Skipping $i"
        continue
    fi
    echo "Number: $i"
done

# select 循环(交互式菜单)
echo "Select an option:"
select option in "Option 1" "Option 2" "Option 3" "Exit"; do
    case $option in
        "Option 1")
            echo "You selected Option 1"
            ;;
        "Option 2")
            echo "You selected Option 2"
            ;;
        "Option 3")
            echo "You selected Option 3"
            ;;
        "Exit")
            echo "Exiting..."
            break
            ;;
        *)
            echo "Invalid option"
            ;;
    esac
done

2.3.3 案例语句高级用法

bash
# 基本 case 语句
case $1 in
    start)
        echo "Starting service"
        ;;
    stop)
        echo "Stopping service"
        ;;
    restart)
        echo "Restarting service"
        ;;
    status)
        echo "Checking status"
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

# 模式匹配
file_type() {
    local file=$1
    case $file in
        *.txt)
            echo "Text file"
            ;;
        *.sh)
            echo "Shell script"
            ;;
        *.py)
            echo "Python script"
            ;;
        *.{jpg,jpeg,png,gif})
            echo "Image file"
            ;;
        *)
            echo "Unknown file type"
            ;;
    esac
}

file_type "example.txt"
file_type "script.sh"
file_type "image.jpg"

# 正则表达式匹配(bash)
regex_test() {
    local input=$1
    case $input in
        [0-9]+)
            echo "Number"
            ;;
        [a-zA-Z]+)
            echo "Letters only"
            ;;
        [a-zA-Z0-9]+)
            echo "Alphanumeric"
            ;;
        *)
            echo "Other"
            ;;
    esac
}

regex_test "123"
regex_test "abc"
regex_test "abc123"
regex_test "abc-123"

3. Shell脚本调试技巧

3.1 调试模式

bash
# 调试模式执行脚本
bash -x script.sh

# 在脚本中设置调试模式
#!/bin/bash
set -x  # 开启调试模式
# 脚本内容
set +x  # 关闭调试模式

# 详细调试模式
bash -v script.sh  # 显示脚本内容后执行
bash -n script.sh  # 检查语法错误,不执行

# 调试选项
set -e  # 遇到错误立即退出
set -u  # 遇到未定义变量立即退出
set -o pipefail  # 管道命令失败时退出
set -x  # 显示执行的命令

# 组合选项
set -euo pipefail

3.2 日志记录

bash
# 基本日志函数
log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}

log_debug() {
    if [ "$DEBUG" = "true" ]; then
        echo "[DEBUG] $(date '+%Y-%m-%d %H:%M:%S') - $1"
    fi
}

# 使用日志函数
DEBUG=true
log_info "Starting script"
log_debug "Debug information"
log_error "An error occurred"

# 日志重定向
exec > >(tee -a script.log) 2>&1  # 同时输出到终端和日志文件

# 带时间戳的日志
exec > >(while read line; do echo "$(date '+%Y-%m-%d %H:%M:%S') $line"; done >> script.log) 2>&1

3.3 错误处理

bash
# 错误处理函数
error_exit() {
    local error_message="${1:-"Unknown error"}"
    echo "Error: $error_message" >&2
    exit 1
}

# 使用错误处理
if [ ! -f "file.txt" ]; then
    error_exit "File not found"
fi

# 陷阱处理
trap 'error_exit "Script interrupted"' INT TERM

trap 'echo "Cleaning up..."; rm -f temp.txt' EXIT

# 命令执行状态检查
command || error_exit "Command failed"

# 安全执行
set -euo pipefail

# 恢复执行状态
set +e
command
status=$?
set -e
if [ $status -ne 0 ]; then
    error_exit "Command failed with status $status"
fi

4. Shell脚本的高级特性

4.1 进程管理

bash
# 后台执行
command &

# 等待后台进程
wait $!

# 进程ID
echo "Current PID: $$"
echo "Parent PID: $PPID"

# 进程状态检查
if ps -p $PID > /dev/null; then
    echo "Process is running"
else
    echo "Process is not running"
fi

# 杀死进程
kill $PID
kill -9 $PID  # 强制杀死

# 进程优先级
nice -n 10 command  # 低优先级
renice 10 -p $PID  # 调整优先级

# 后台运行脚本
nohup script.sh > script.out 2>&1 &

# 定时执行
echo "command" | at now + 1 minute

# 周期性执行
watch -n 5 command  # 每5秒执行一次

4.2 信号处理

bash
# 信号处理函数
handle_signal() {
    local signal=$1
    echo "Received signal $signal"
    case $signal in
        SIGINT)
            echo "Interrupted by user"
            ;;
        SIGTERM)
            echo "Termination request"
            ;;
        SIGUSR1)
            echo "User-defined signal 1"
            ;;
        SIGUSR2)
            echo "User-defined signal 2"
            ;;
    esac
}

# 设置信号陷阱
trap 'handle_signal SIGINT' INT
trap 'handle_signal SIGTERM' TERM
trap 'handle_signal SIGUSR1' USR1
trap 'handle_signal SIGUSR2' USR2

# 发送信号
kill -USR1 $$
kill -USR2 $$

# 清理陷阱
trap - INT TERM USR1 USR2

# 退出陷阱
trap 'echo "Exiting..."; cleanup' EXIT

# 错误陷阱
trap 'echo "Error occurred at line $LINENO"; exit 1' ERR

4.3 输入输出重定向

bash
# 标准输出重定向
command > output.txt        # 覆盖输出
command >> output.txt       # 追加输出

# 标准错误重定向
command 2> error.txt        # 错误输出到文件
command 2>&1                # 错误重定向到标准输出
command > output.txt 2>&1   # 所有输出到文件
command &> output.txt       # 所有输出到文件(bash)

# 输入重定向
command < input.txt         # 从文件读取输入
command << EOF              # Here文档
Line 1
Line 2
EOF

command <<< "Hello"         # Here字符串(bash)

# 管道
command1 | command2         # 管道连接
command1 | tee output.txt   #  tee 命令
command1 | xargs command2   # xargs 命令

# 进程替换
diff <(command1) <(command2)  # 比较两个命令的输出

# 文件描述符
 exec 3> output.txt        # 创建文件描述符3
 echo "Hello" >&3          # 写入文件描述符3
 exec 3>&-                 # 关闭文件描述符3

# 复制文件描述符
exec 3>&1                  # 保存标准输出到文件描述符3
echo "To stdout"          # 输出到标准输出
echo "To file" >&3        # 输出到保存的标准输出
exec 3>&-                 # 关闭文件描述符3

4.4 命令替换与扩展

bash
# 命令替换
current_dir=$(pwd)
echo "Current directory: $current_dir"

date=$(date '+%Y-%m-%d')
echo "Today is $date"

# 算术扩展
result=$((1 + 2 * 3))
echo "Result: $result"

# 变量扩展
var="Hello"
echo "${var}"
echo "${var}_world"
echo "${var:-default}"
echo "${var:+value}"
echo "${var:?error}"
echo "${var:=default}"

# 字符串扩展
echo "${var:0:2}"    # 子字符串
echo "${var#He}"     # 移除前缀
echo "${var%lo}"     # 移除后缀
echo "${var/He/Hi}"   # 替换

echo "${var^^}"     # 转为大写(bash 4+)
echo "${var,,}"     # 转为小写(bash 4+)

# 路径扩展
echo "~"           # 家目录
echo "~user"       # 指定用户的家目录
echo "$HOME"       # 家目录

echo "$(dirname "$0")"  # 脚本所在目录
echo "$(basename "$0")"  # 脚本文件名

# 引号扩展
single_quoted='This is $var'  # 单引号不扩展变量
double_quoted="This is $var"  # 双引号扩展变量
echo $single_quoted
echo $double_quoted

5. Shell脚本实战案例

5.1 系统监控脚本

bash
#!/bin/bash

# 系统监控脚本
set -euo pipefail

# 日志函数
log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}

# 监控配置
CPU_THRESHOLD=80
MEMORY_THRESHOLD=80
DISK_THRESHOLD=80
CHECK_INTERVAL=60
LOG_FILE="system_monitor.log"

# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1

# 检查CPU使用率
check_cpu() {
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
    local cpu_usage_int=$(printf "%.0f" "$cpu_usage")
    
    log_info "CPU usage: $cpu_usage%"
    
    if [ "$cpu_usage_int" -gt "$CPU_THRESHOLD" ]; then
        log_error "CPU usage exceeds threshold ($CPU_THRESHOLD%)"
        return 1
    fi
    
    return 0
}

# 检查内存使用率
check_memory() {
    local memory_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}')
    local memory_usage_int=$(printf "%.0f" "$memory_usage")
    
    log_info "Memory usage: $memory_usage%"
    
    if [ "$memory_usage_int" -gt "$MEMORY_THRESHOLD" ]; then
        log_error "Memory usage exceeds threshold ($MEMORY_THRESHOLD%)"
        return 1
    fi
    
    return 0
}

# 检查磁盘使用率
check_disk() {
    local disk_usage=$(df -h / | grep '/' | awk '{print $5}' | sed 's/%//')
    
    log_info "Disk usage: $disk_usage%"
    
    if [ "$disk_usage" -gt "$DISK_THRESHOLD" ]; then
        log_error "Disk usage exceeds threshold ($DISK_THRESHOLD%)"
        return 1
    fi
    
    return 0
}

# 检查进程状态
check_process() {
    local process_name=$1
    
    if pgrep "$process_name" > /dev/null; then
        log_info "Process $process_name is running"
        return 0
    else
        log_error "Process $process_name is not running"
        return 1
    fi
}

# 主函数
main() {
    log_info "Starting system monitor"
    
    while true; do
        log_info "-----------------------------------"
        log_info "Checking system status"
        
        check_cpu
        check_memory
        check_disk
        check_process "sshd"
        check_process "nginx"
        
        log_info "System check completed"
        log_info "Sleeping for $CHECK_INTERVAL seconds"
        sleep "$CHECK_INTERVAL"
    done
}

# 执行主函数
main

5.2 备份脚本

bash
#!/bin/bash

# 备份脚本
set -euo pipefail

# 配置
BACKUP_DIR="/backup"
SOURCE_DIRS=("/etc" "/var/www" "/home")
EXCLUDE_PATTERNS=("*.tmp" "*.log" "/home/*/.cache" "/home/*/.local/share")
RETENTION_DAYS=7
LOG_FILE="backup.log"

# 日志函数
log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}

# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1

# 创建备份目录
create_backup_dir() {
    local backup_date=$(date '+%Y-%m-%d_%H-%M-%S')
    local backup_path="$BACKUP_DIR/$backup_date"
    
    mkdir -p "$backup_path"
    echo "$backup_path"
}

# 执行备份
perform_backup() {
    local backup_path=$1
    
    log_info "Starting backup to $backup_path"
    
    # 构建 rsync 命令
    local rsync_cmd="rsync -av --delete"
    
    # 添加排除模式
    for pattern in "${EXCLUDE_PATTERNS[@]}"; do
        rsync_cmd+=" --exclude='$pattern'"
    done
    
    # 执行备份
    for dir in "${SOURCE_DIRS[@]}"; do
        log_info "Backing up $dir"
        eval "$rsync_cmd '$dir' '$backup_path/'"
    done
    
    log_info "Backup completed successfully"
}

# 创建压缩包
create_archive() {
    local backup_path=$1
    local archive_name="${backup_path}.tar.gz"
    
    log_info "Creating archive $archive_name"
    
    tar -czf "$archive_name" -C "$(dirname "$backup_path")" "$(basename "$backup_path")"
    
    # 删除原始备份目录
    rm -rf "$backup_path"
    
    log_info "Archive created successfully"
    echo "$archive_name"
}

# 清理旧备份
cleanup_old_backups() {
    log_info "Cleaning up backups older than $RETENTION_DAYS days"
    
    find "$BACKUP_DIR" -name "*.tar.gz" -mtime +"$RETENTION_DAYS" -delete
    
    log_info "Cleanup completed"
}

# 主函数
main() {
    log_info "Starting backup script"
    
    # 创建备份目录
    local backup_path=$(create_backup_dir)
    
    # 执行备份
    perform_backup "$backup_path"
    
    # 创建压缩包
    local archive_name=$(create_archive "$backup_path")
    
    # 清理旧备份
    cleanup_old_backups
    
    log_info "Backup script completed successfully"
    log_info "Backup archive: $archive_name"
}

# 执行主函数
main

5.3 自动化部署脚本

bash
#!/bin/bash

# 自动化部署脚本
set -euo pipefail

# 配置
REPO_URL="https://github.com/user/repo.git"
APP_DIR="/var/www/app"
TMP_DIR="/tmp/deploy"
BRANCH="main"
ENVIRONMENT="production"
LOG_FILE="deploy.log"

# 日志函数
log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}

# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1

# 检查依赖
check_dependencies() {
    log_info "Checking dependencies"
    
    for cmd in git curl tar; do
        if ! command -v "$cmd" > /dev/null; then
            log_error "Command $cmd not found"
            exit 1
        fi
    done
    
    log_info "Dependencies check completed"
}

# 准备部署目录
prepare_deploy_dir() {
    log_info "Preparing deploy directory"
    
    rm -rf "$TMP_DIR"
    mkdir -p "$TMP_DIR"
    
    log_info "Deploy directory prepared"
}

# 克隆代码
clone_code() {
    log_info "Cloning code from $REPO_URL"
    
    git clone --branch "$BRANCH" "$REPO_URL" "$TMP_DIR"
    
    log_info "Code cloned successfully"
}

# 安装依赖
install_dependencies() {
    log_info "Installing dependencies"
    
    cd "$TMP_DIR"
    
    if [ -f "package.json" ]; then
        npm install
    elif [ -f "requirements.txt" ]; then
        pip install -r requirements.txt
    elif [ -f "pom.xml" ]; then
        mvn install
    else
        log_info "No dependency file found"
    fi
    
    log_info "Dependencies installed successfully"
}

# 构建项目
build_project() {
    log_info "Building project"
    
    cd "$TMP_DIR"
    
    if [ -f "package.json" ]; then
        npm run build
    elif [ -f "pom.xml" ]; then
        mvn package
    else
        log_info "No build script found"
    fi
    
    log_info "Project built successfully"
}

# 部署应用
deploy_application() {
    log_info "Deploying application to $APP_DIR"
    
    # 停止服务
    if systemctl is-active --quiet nginx; then
        systemctl stop nginx
    fi
    
    # 备份旧应用
    if [ -d "$APP_DIR" ]; then
        backup_dir="$APP_DIR.$(date '+%Y-%m-%d_%H-%M-%S')"
        mv "$APP_DIR" "$backup_dir"
        log_info "Old application backed up to $backup_dir"
    fi
    
    # 复制新应用
    mkdir -p "$APP_DIR"
    cp -r "$TMP_DIR"/* "$APP_DIR/"
    
    # 启动服务
    if systemctl is-enabled --quiet nginx; then
        systemctl start nginx
    fi
    
    log_info "Application deployed successfully"
}

# 验证部署
verify_deployment() {
    log_info "Verifying deployment"
    
    # 检查服务状态
    if systemctl is-active --quiet nginx; then
        log_info "Nginx service is running"
    else
        log_error "Nginx service is not running"
        return 1
    fi
    
    # 检查应用响应
    if curl -s -o /dev/null -w "%{http_code}" "http://localhost" | grep -q "200"; then
        log_info "Application is responding"
    else
        log_error "Application is not responding"
        return 1
    fi
    
    log_info "Deployment verified successfully"
    return 0
}

# 清理临时文件
cleanup() {
    log_info "Cleaning up temporary files"
    
    rm -rf "$TMP_DIR"
    
    log_info "Cleanup completed"
}

# 回滚部署
rollback() {
    log_error "Deployment failed, rolling back"
    
    # 查找最新备份
    local backup=$(ls -1 "$APP_DIR.*" 2>/dev/null | sort -r | head -n 1)
    
    if [ -n "$backup" ]; then
        log_info "Rolling back to $backup"
        
        # 停止服务
        if systemctl is-active --quiet nginx; then
            systemctl stop nginx
        fi
        
        # 恢复备份
        rm -rf "$APP_DIR"
        mv "$backup" "$APP_DIR"
        
        # 启动服务
        if systemctl is-enabled --quiet nginx; then
            systemctl start nginx
        fi
        
        log_info "Rollback completed"
    else
        log_error "No backup found for rollback"
    fi
}

# 主函数
main() {
    log_info "Starting deployment script"
    log_info "Environment: $ENVIRONMENT"
    log_info "Branch: $BRANCH"
    
    # 检查依赖
    check_dependencies
    
    # 准备部署目录
    prepare_deploy_dir
    
    # 克隆代码
    clone_code
    
    # 安装依赖
    install_dependencies
    
    # 构建项目
    build_project
    
    # 部署应用
    deploy_application
    
    # 验证部署
    if ! verify_deployment; then
        rollback
        cleanup
        exit 1
    fi
    
    # 清理临时文件
    cleanup
    
    log_info "Deployment script completed successfully"
}

# 执行主函数
main

5.4 网络检测脚本

bash
#!/bin/bash

# 网络检测脚本
set -euo pipefail

# 配置
HOSTS=("google.com" "github.com" "1.1.1.1" "8.8.8.8")
PORT=80
TIMEOUT=5
LOG_FILE="network_check.log"

# 日志函数
log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}

# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1

# 检查主机可达性
check_host() {
    local host=$1
    
    log_info "Checking host $host"
    
    if ping -c 1 -W "$TIMEOUT" "$host" > /dev/null; then
        log_info "$host is reachable"
        return 0
    else
        log_error "$host is not reachable"
        return 1
    fi
}

# 检查端口可达性
check_port() {
    local host=$1
    local port=$2
    
    log_info "Checking port $port on $host"
    
    if nc -z -w "$TIMEOUT" "$host" "$port"; then
        log_info "Port $port on $host is open"
        return 0
    else
        log_error "Port $port on $host is closed or filtered"
        return 1
    fi
}

# 检查HTTP响应
check_http() {
    local host=$1
    local port=$2
    
    log_info "Checking HTTP response from $host:$port"
    
    local url="http://$host:$port"
    local response=$(curl -s -o /dev/null -w "%{http_code}" -m "$TIMEOUT" "$url")
    
    if [ "$response" -eq 200 ]; then
        log_info "HTTP response from $host:$port is $response (OK)"
        return 0
    else
        log_error "HTTP response from $host:$port is $response"
        return 1
    fi
}

# 检查网络接口
check_interfaces() {
    log_info "Checking network interfaces"
    
    ip addr | grep -E 'inet ' | grep -v '127.0.0.1'
    
    log_info "Network interfaces checked"
}

# 检查路由表
check_routes() {
    log_info "Checking routing table"
    
    ip route
    
    log_info "Routing table checked"
}

# 主函数
main() {
    log_info "Starting network check"
    
    # 检查网络接口
    check_interfaces
    
    # 检查路由表
    check_routes
    
    # 检查每个主机
    for host in "${HOSTS[@]}"; do
        log_info "-----------------------------------"
        check_host "$host"
        check_port "$host" "$PORT"
        check_http "$host" "$PORT"
    done
    
    log_info "Network check completed"
}

# 执行主函数
main

5.5 日志分析脚本

bash
#!/bin/bash

# 日志分析脚本
set -euo pipefail

# 配置
LOG_FILES=("/var/log/syslog" "/var/log/nginx/access.log" "/var/log/nginx/error.log")
OUTPUT_DIR="/tmp/log_analysis"
TOP_N=10
LOG_FILE="log_analysis.log"

# 日志函数
log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}

# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1

# 创建输出目录
create_output_dir() {
    mkdir -p "$OUTPUT_DIR"
    log_info "Created output directory $OUTPUT_DIR"
}

# 分析系统日志
analyze_syslog() {
    local log_file="/var/log/syslog"
    
    if [ -f "$log_file" ]; then
        log_info "Analyzing system log $log_file"
        
        # 提取错误信息
        grep -i "error" "$log_file" > "$OUTPUT_DIR/syslog_errors.txt"
        
        # 统计错误类型
        cut -d' ' -f5- "$OUTPUT_DIR/syslog_errors.txt" | sort | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/syslog_error_stats.txt"
        
        log_info "System log analysis completed"
    else
        log_error "System log file $log_file not found"
    fi
}

# 分析Nginx访问日志
analyze_nginx_access() {
    local log_file="/var/log/nginx/access.log"
    
    if [ -f "$log_file" ]; then
        log_info "Analyzing Nginx access log $log_file"
        
        # 提取IP地址
        awk '{print $1}' "$log_file" | sort | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/nginx_top_ips.txt"
        
        # 提取请求路径
        awk '{print $7}' "$log_file" | sort | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/nginx_top_paths.txt"
        
        # 提取状态码
        awk '{print $9}' "$log_file" | sort | uniq -c | sort -nr > "$OUTPUT_DIR/nginx_status_codes.txt"
        
        # 提取用户代理
        awk -F\" '{print $6}' "$log_file" | sort | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/nginx_top_user_agents.txt"
        
        log_info "Nginx access log analysis completed"
    else
        log_error "Nginx access log file $log_file not found"
    fi
}

# 分析Nginx错误日志
analyze_nginx_error() {
    local log_file="/var/log/nginx/error.log"
    
    if [ -f "$log_file" ]; then
        log_info "Analyzing Nginx error log $log_file"
        
        # 提取错误信息
        sort "$log_file" | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/nginx_error_stats.txt"
        
        log_info "Nginx error log analysis completed"
    else
        log_error "Nginx error log file $log_file not found"
    fi
}

# 生成报告
generate_report() {
    local report_file="$OUTPUT_DIR/report.txt"
    
    log_info "Generating report $report_file"
    
    cat > "$report_file" << EOF
Log Analysis Report
==================
Date: $(date '+%Y-%m-%d %H:%M:%S')

System Log Analysis
------------------
Top $TOP_N Error Types:
$(cat "$OUTPUT_DIR/syslog_error_stats.txt" 2>/dev/null || echo "No data")

Nginx Access Log Analysis
------------------------
Top $TOP_N IP Addresses:
$(cat "$OUTPUT_DIR/nginx_top_ips.txt" 2>/dev/null || echo "No data")

Top $TOP_N Request Paths:
$(cat "$OUTPUT_DIR/nginx_top_paths.txt" 2>/dev/null || echo "No data")

Status Codes:
$(cat "$OUTPUT_DIR/nginx_status_codes.txt" 2>/dev/null || echo "No data")

Top $TOP_N User Agents:
$(cat "$OUTPUT_DIR/nginx_top_user_agents.txt" 2>/dev/null || echo "No data")

Nginx Error Log Analysis
-----------------------
Top $TOP_N Error Types:
$(cat "$OUTPUT_DIR/nginx_error_stats.txt" 2>/dev/null || echo "No data")
EOF
    
    log_info "Report generated successfully"
    cat "$report_file"
}

# 主函数
main() {
    log_info "Starting log analysis"
    
    # 创建输出目录
    create_output_dir
    
    # 分析系统日志
    analyze_syslog
    
    # 分析Nginx访问日志
    analyze_nginx_access
    
    # 分析Nginx错误日志
    analyze_nginx_error
    
    # 生成报告
    generate_report
    
    log_info "Log analysis completed"
    log_info "Results saved to $OUTPUT_DIR"
}

# 执行主函数
main

4. Shell脚本性能优化

4.1 性能优化技巧

4.1.1 减少命令执行

bash
# 不好的写法
for file in *.txt; do
    wc -l $file
    grep "error" $file
    sort $file > sorted_$file
done

# 好的写法
for file in *.txt; do
    lines=$(wc -l < $file)
    errors=$(grep -c "error" $file)
    echo "$file: $lines lines, $errors errors"
    sort $file > sorted_$file
done

4.1.2 使用内置命令

bash
# 不好的写法
for i in $(seq 1 1000); do
    echo $i
done

# 好的写法
for ((i=1; i<=1000; i++)); do
    echo $i
done

# 不好的写法
echo "Current directory: $(pwd)"

# 好的写法
echo "Current directory: $PWD"

4.1.3 减少管道和重定向

bash
# 不好的写法
cat file.txt | grep "pattern" | sort | uniq > output.txt

# 好的写法
grep "pattern" file.txt | sort -u > output.txt

# 不好的写法
for file in *.txt; do
    cat $file >> all.txt
done

# 好的写法
cat *.txt > all.txt

4.1.4 使用更高效的命令

bash
# 不好的写法
for i in $(ls *.txt); do
    echo $i
done

# 好的写法
for file in *.txt; do
    echo $file
done

# 不好的写法
find . -name "*.txt" | xargs grep "pattern"

# 好的写法
find . -name "*.txt" -exec grep "pattern" {} \;

4.1.5 内存管理

bash
# 不好的写法
large_file=$(cat large.txt)
echo "$large_file"

# 好的写法
cat large.txt

# 不好的写法
for line in $(cat file.txt); do
    echo $line
done

# 好的写法
while IFS= read -r line; do
    echo $line
done < file.txt

4.2 性能测试

bash
# 测试脚本执行时间
time script.sh

# 详细时间统计
time -p script.sh

# 内存使用
time -v script.sh

# 比较不同方法的性能
method1() {
    for ((i=1; i<=10000; i++)); do
        echo $i > /dev/null
    done
}

method2() {
    seq 1 10000 > /dev/null
}

echo "Method 1:"
time method1
echo "Method 2:"
time method2

# 分析脚本执行
trace() {
    local script=$1
    bash -x "$script" 2>&1 | grep -E '^\+ ' | head -n 20
}

trace script.sh

5. Shell脚本安全最佳实践

5.1 安全隐患

5.1.1 常见安全问题

  • 命令注入:使用未过滤的用户输入
  • 路径遍历:允许访问不应访问的文件
  • 权限提升:以 root 权限执行不必要的操作
  • 敏感信息泄露:在日志中记录密码等敏感信息
  • 不安全的临时文件:使用可预测的临时文件名
  • 脚本注入:通过环境变量或配置文件注入恶意代码

5.1.2 防范措施

  • 输入验证:验证所有用户输入
  • 使用引用:正确引用变量,防止命令注入
  • 最小权限:以最小必要权限执行脚本
  • 安全的临时文件:使用 mktemp 创建临时文件
  • 敏感信息保护:避免在日志中记录敏感信息
  • 文件权限:设置正确的文件权限
  • 环境变量清理:清理不必要的环境变量

5.2 安全编码实践

bash
# 输入验证
validate_input() {
    local input=$1
    if [[ ! $input =~ ^[a-zA-Z0-9_-]+$ ]]; then
        echo "Invalid input"
        exit 1
    fi
}

# 使用引用
user_input="$(echo "$1" | sed 's/[^a-zA-Z0-9_-]//g')"
echo "Processing: "$user_input""

# 最小权限
if [ "$(id -u)" -eq 0 ]; then
    echo "Do not run as root"
    exit 1
fi

# 安全的临时文件
temp_file=$(mktemp /tmp/script.XXXXXX)
trap "rm -f '$temp_file'" EXIT

# 敏感信息保护
password="$(echo "$2" | sed 's/./*/g')"
echo "Password: $password"

# 文件权限
chmod 700 script.sh

# 环境变量清理
unset HISTFILE
unset SSH_AUTH_SOCK

# 安全的命令执行
command=$(printf "%q" "$user_command")
eval "$command"

# 检查文件存在性
if [ -f "$file" ]; then
    # 安全地读取文件
    while IFS= read -r line; do
        echo "$line"
    done < "$file"
fi

# 防止路径遍历
file=$(realpath "$file")
base_dir=$(realpath "/safe/dir")

if [[ "$file" == "$base_dir"* ]]; then
    # 安全地访问文件
    cat "$file"
else
    echo "Access denied"
    exit 1
fi

6. 总结与展望

6.1 Shell脚本的优势

  • 简单易用:语法简洁,易于学习和使用
  • 强大的文本处理能力:擅长处理文本数据
  • 系统集成:与操作系统紧密集成
  • 跨平台:在大多数UNIX/Linux系统上可用
  • 无需编译:直接执行,快速开发
  • 丰富的命令:可以调用系统的各种命令

6.2 Shell脚本的局限性

  • 性能:相比编译型语言,执行速度较慢
  • 复杂逻辑:处理复杂逻辑时代码可能变得难以维护
  • 可移植性:不同Shell之间存在差异
  • 错误处理:错误处理机制相对简单
  • 安全性:存在安全隐患,需要特别注意
  • 调试:调试工具相对有限

6.3 Shell脚本的未来发展

  • bash 5+:提供更多高级特性,如关联数组、模式匹配等
  • zsh:提供更丰富的功能和更好的用户体验
  • fish:提供更现代的Shell体验,注重用户友好性
  • ShellCheck:静态分析工具,帮助发现脚本中的问题
  • shfmt:Shell脚本格式化工具,提高代码质量
  • Docker:结合容器技术,实现更一致的执行环境
  • 云原生:在云环境中用于自动化部署和管理

6.4 学习建议

  1. 掌握基础:熟悉Shell脚本的基本语法和命令
  2. 实践项目:通过实际项目练习,巩固所学知识
  3. 学习高级特性:掌握函数、数组、正则表达式等高级特性
  4. 阅读优秀脚本:学习他人编写的优秀脚本
  5. 使用工具:使用ShellCheck等工具检查脚本质量
  6. 关注安全:了解Shell脚本的安全隐患和防范措施
  7. 持续学习:关注Shell的最新发展和最佳实践
  8. 结合其他工具:与Python、Perl等语言结合使用

6.5 资源推荐

通过本文的学习,您应该已经掌握了Shell脚本的高级特性和应用场景。Shell脚本是一种强大的工具,可以帮助您自动化各种系统管理任务,提高工作效率。随着您的不断实践和学习,您将能够编写更加复杂、高效、安全的Shell脚本,解决实际工作中的各种问题。

评论区

专业的Linux技术学习平台,从入门到精通的完整学习路径