Shell 脚本编程全解析:从入门到企业级实战

Shell 脚本编程全解析:从入门到企业级实战

一、Shell 脚本基础认知

1.1 什么是 Shell

Shell 是一种命令行解释器,它为用户提供了与操作系统内核交互的接口。简单来说,Shell 就是用户与 Linux 系统之间的 "翻译官",将用户的命令转换为内核能够理解的指令。

而 Shell 脚本(Shell Script)则是将一系列 Shell 命令按顺序保存到文本文件中,赋予执行权限后可以批量执行的脚本文件。它类似于 Windows 系统的批处理文件(.bat),但功能更加强大、语法更加灵活。

1.2 Shell 的种类

Linux 系统中常见的 Shell 有多种类型:

  • Bash(Bourne Again SHell):最常用的 Shell,是 Bourne Shell 的增强版,兼容 Bourne Shell 并增加了许多新特性
  • Sh(Bourne Shell):最早的 Unix Shell,功能相对简单
  • Csh(C Shell):语法类似 C 语言,有自己的特点
  • Ksh(Korn Shell):结合了 Csh 和 Sh 的优点
  • Zsh:功能更强大的 Shell,兼容 Bash 并增加了更多特性

在主流 Linux 发行版(如 CentOS、Ubuntu)中,默认的 Shell 都是 Bash,因此本文主要围绕 Bash 脚本展开。

可以通过以下命令查看系统默认 Shell:

bash 复制代码
echo $SHELL

查看系统安装的所有 Shell:

bash 复制代码
cat /etc/shells

二、为什么要学习 Shell 脚本编程

2.1 自动化运维的核心工具

在 Linux 服务器管理中,许多重复性工作可以通过 Shell 脚本自动化完成:

  • 系统监控与报警
  • 日志分析与统计
  • 批量部署与配置
  • 定时任务执行
  • 数据备份与恢复

2.2 处理文本文件的优势

Linux 系统中绝大多数配置文件、日志文件都是纯文本格式,Shell 脚本结合文本处理工具(grep、sed、awk 等)可以高效处理这些文件:

  • 快速提取关键信息
  • 批量修改配置参数
  • 分析日志并生成报告
  • 处理格式化数据

2.3 职业发展的必备技能

对于运维工程师、开发工程师、测试工程师而言,Shell 脚本编程是一项重要技能:

  • 提升工作效率,减少重复劳动
  • 实现复杂的系统管理任务
  • 更好地理解 Linux 系统原理
  • 为学习更复杂的编程语言打下基础

三、Shell 脚本入门准备

3.1 环境准备

学习 Shell 脚本编程不需要特殊的开发环境,只需要:

  1. 一台 Linux 系统的计算机或服务器
  2. 一个文本编辑器(推荐 vim 或 nano)
  3. 基本的 Linux 命令操作能力

3.2 vim 编辑器配置

vim 是编写 Shell 脚本的理想工具,以下是一些实用配置:

bash 复制代码
# 创建或编辑 vim 配置文件
vim ~/.vimrc

# 添加以下配置
set number          # 显示行号
set syntax on       # 语法高亮
set autoindent      # 自动缩进
set smartindent     # 智能缩进
set tabstop=4       # Tab 键宽度
set shiftwidth=4    # 自动缩进宽度
set showmatch       # 括号匹配显示

配置生效后,使用 vim 编辑脚本会更加高效。

四、第一个 Shell 脚本

4.1 脚本创建与执行

创建一个简单的 Shell 脚本:

bash 复制代码
# 创建脚本文件
vim hello.sh

# 输入以下内容
#!/bin/bash
# 这是一个简单的 Shell 脚本
echo "Hello, World!"
echo "当前时间: $(date)"
echo "当前用户: $USER"
echo "当前目录: $(pwd)"

脚本执行方法:

  1. 使用 bash 命令执行(不需要执行权限):bash hello.sh

  2. 赋予执行权限后直接执行

bash 复制代码
# 添加执行权限
chmod +x hello.sh

# 执行脚本
./hello.sh
  1. 使用绝对路径执行
bash 复制代码
# 假设脚本在 /home/user 目录下
/home/user/hello.sh
  1. 使用 source 命令执行(在当前 Shell 环境中执行):
bash 复制代码
source hello.sh
# 或
. hello.sh

4.2 脚本结构解析

一个规范的 Shell 脚本通常包含以下几个部分:

  1. 解释器声明 :第一行的 #!/bin/bash 指定脚本使用 bash 解释器
  2. 注释信息 :以 # 开头的行,用于说明脚本功能、作者、版本等
  3. 脚本主体:具体的命令和控制结构
bash 复制代码
#!/bin/bash
# 脚本名称: system_info.sh
# 作    者: Your Name
# 日    期: 2023-07-01
# 版    本: 1.0
# 功    能: 显示系统基本信息

echo "=== 系统信息 ==="
uname -a

echo -e "\n=== CPU 信息 ==="
lscpu | grep 'Model name\|CPU(s)'

echo -e "\n=== 内存信息 ==="
free -h

echo -e "\n=== 磁盘信息 ==="
df -h

五、Shell 变量与数据类型

5.1 变量定义与使用

Shell 变量不需要声明类型,直接定义即可:

bash 复制代码
#!/bin/bash

# 定义变量(等号前后不能有空格)
name="John"
age=30
height=1.75

# 使用变量(在变量名前加 $)
echo "姓名: $name"
echo "年龄: $age"
echo "身高: $height"

# 变量重新赋值
age=31
echo "修改后的年龄: $age"

# 变量拼接
greeting="Hello, "$name"!"
echo $greeting

5.2 系统环境变量

系统预设了一些环境变量,可以直接使用:

bash 复制代码
#!/bin/bash

echo "当前用户: $USER"
echo "用户家目录: $HOME"
echo "当前工作目录: $PWD"
echo "命令搜索路径: $PATH"
echo "主机名: $HOSTNAME"
echo "Shell 类型: $SHELL"
echo "终端类型: $TERM"
echo "进程 ID: $$"  # 当前脚本的进程 ID
echo "退出状态: $?"  # 上一条命令的退出状态,0 表示成功

5.3 位置参数变量

用于获取脚本执行时传入的参数:

bash 复制代码
#!/bin/bash

echo "脚本名称: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "第三个参数: $3"
echo "所有参数: $*"  # 所有参数作为一个整体
echo "所有参数: $@"  # 所有参数作为独立个体
echo "参数个数: $#"  # 参数的数量

执行示例:

bash 复制代码
./params.sh first second third

5.4 变量操作

bash 复制代码
#!/bin/bash

# 字符串长度
str="Hello World"
echo "字符串长度: ${#str}"

# 字符串截取
echo "从位置 1 开始截取: ${str:1}"
echo "从位置 1 开始截取 5 个字符: ${str:1:5}"

# 字符串替换
echo "替换第一个匹配: ${str/Hello/Hi}"
echo "替换所有匹配: ${str//o/O}"

# 变量默认值
echo "变量存在: ${name:-默认值}"  # 如果 name 未定义,使用默认值
name="John"
echo "变量存在: ${name:-默认值}"  # 如果 name 已定义,使用变量值

六、Shell 运算符与表达式

6.1 算术运算符

bash 复制代码
#!/bin/bash

a=10
b=3

# 算术运算方式 1: 使用 $((...))
echo "a + b = $((a + b))"
echo "a - b = $((a - b))"
echo "a * b = $((a * b))"
echo "a / b = $((a / b))"
echo "a % b = $((a % b))"
echo "a^2 = $((a **2))"

# 算术运算方式 2: 使用 $[...])
echo "a + b = $[a + b]"

# 算术运算方式 3: 使用 expr 命令
echo "a + b = $(expr $a + $b)"
echo "a * b = $(expr $a \* $b)"  # 注意乘法需要转义

6.2 赋值运算符

bash 复制代码
#!/bin/bash

a=10

# 基本赋值
b=$a
echo "b = $b"

# 复合赋值
b=5
b+=3  # 等价于 b=$((b + 3))
echo "b += 3 后: $b"

b=5
b*=2  # 等价于 b=$((b * 2))
echo "b *= 2 后: $b"

6.3 比较运算符

6.3.1 数值比较
bash 复制代码
#!/bin/bash

a=10
b=20

# 使用 [ ] 进行比较,注意空格
if [ $a -eq $b ]; then
    echo "$a 等于 $b"
else
    echo "$a 不等于 $b"
fi

if [ $a -gt $b ]; then
    echo "$a 大于 $b"
else
    echo "$a 不大于 $b"
fi

# 常用数值比较运算符
# -eq: 等于
# -ne: 不等于
# -gt: 大于
# -lt: 小于
# -ge: 大于等于
# -le: 小于等于
6.3.2 字符串比较
bash 复制代码
#!/bin/bash

str1="hello"
str2="world"
str3="hello"

if [ "$str1" = "$str2" ]; then
    echo "$str1 等于 $str2"
else
    echo "$str1 不等于 $str2"
fi

if [ "$str1" = "$str3" ]; then
    echo "$str1 等于 $str3"
else
    echo "$str1 不等于 $str3"
fi

# 字符串长度比较
if [ -z "$str1" ]; then
    echo "$str1 是空字符串"
else
    echo "$str1 不是空字符串,长度为 ${#str1}"
fi

# 常用字符串比较运算符
# =: 等于
# !=: 不等于
# -z: 字符串长度为 0
# -n: 字符串长度不为 0

6.4 逻辑运算符

bash 复制代码
#!/bin/bash

a=10
b=20
c=30

# 逻辑与 -a 或 &&
if [ $a -lt $b -a $b -lt $c ]; then
    echo "$a < $b < $c 成立"
fi

if [ $a -lt $b ] && [ $b -lt $c ]; then
    echo "$a < $b < $c 成立"
fi

# 逻辑或 -o 或 ||
if [ $a -gt $b -o $b -lt $c ]; then
    echo "条件成立"
fi

if [ $a -gt $b ] || [ $b -lt $c ]; then
    echo "条件成立"
fi

# 逻辑非 !
if [ ! $a -gt $b ]; then
    echo "$a 不大于 $b"
fi

6.5 文件测试运算符

bash 复制代码
#!/bin/bash

file="test.txt"
dir="/tmp"

# 检查文件是否存在
if [ -e "$file" ]; then
    echo "$file 存在"
else
    echo "$file 不存在"
    touch "$file"
    echo "已创建 $file"
fi

# 检查是否为普通文件
if [ -f "$file" ]; then
    echo "$file 是普通文件"
fi

# 检查是否为目录
if [ -d "$dir" ]; then
    echo "$dir 是目录"
fi

# 检查文件是否可执行
if [ -x "$file" ]; then
    echo "$file 可执行"
else
    echo "$file 不可执行"
    chmod +x "$file"
    echo "已设置 $file 可执行权限"
fi

# 常用文件测试运算符
# -e: 文件或目录存在
# -f: 是普通文件
# -d: 是目录
# -r: 可读
# -w: 可写
# -x: 可执行
# -s: 文件大小不为 0
# -L: 是符号链接

七、流程控制语句

7.1 if 条件语句

7.1.1 基本语法
bash 复制代码
#!/bin/bash

# 单分支 if 语句
age=18
if [ $age -ge 18 ]; then
    echo "已成年"
fi

# 双分支 if 语句
score=75
if [ $score -ge 60 ]; then
    echo "成绩合格"
else
    echo "成绩不合格"
fi

# 多分支 if 语句
if [ $score -ge 90 ]; then
    echo "优秀"
elif [ $score -ge 80 ]; then
    echo "良好"
elif [ $score -ge 60 ]; then
    echo "及格"
else
    echo "不及格"
fi
7.1.2 实际应用示例
bash 复制代码
#!/bin/bash
# 检查服务是否运行

service_name="sshd"

# 检查服务状态
if systemctl is-active --quiet $service_name; then
    echo "$service_name 服务正在运行"
    # 检查服务是否设置为开机启动
    if systemctl is-enabled --quiet $service_name; then
        echo "$service_name 服务已设置为开机启动"
    else
        echo "$service_name 服务未设置为开机启动"
        read -p "是否设置 $service_name 为开机启动? (y/n) " choice
        if [ "$choice" = "y" ] || [ "$choice" = "Y" ]; then
            systemctl enable $service_name
            echo "$service_name 已设置为开机启动"
        fi
    fi
else
    echo "$service_name 服务未运行"
    read -p "是否启动 $service_name 服务? (y/n) " choice
    if [ "$choice" = "y" ] || [ "$choice" = "Y" ]; then
        systemctl start $service_name
        if [ $? -eq 0 ]; then
            echo "$service_name 服务启动成功"
        else
            echo "$service_name 服务启动失败"
        fi
    fi
fi

7.2 case 语句

当需要匹配多个条件时,case 语句比 if 语句更简洁:

bash 复制代码
#!/bin/bash
# 服务管理脚本

if [ $# -ne 1 ]; then
    echo "用法: $0 [start|stop|restart|status]"
    exit 1
fi

service_name="httpd"

case "$1" in
    start)
        systemctl start $service_name
        echo "$service_name 服务已启动"
        ;;
    stop)
        systemctl stop $service_name
        echo "$service_name 服务已停止"
        ;;
    restart)
        systemctl restart $service_name
        echo "$service_name 服务已重启"
        ;;
    status)
        systemctl status $service_name
        ;;
    *)
        echo "无效参数: $1"
        echo "用法: $0 [start|stop|restart|status]"
        exit 1
        ;;
esac

7.3 for 循环

7.3.1 基本语法
bash 复制代码
#!/bin/bash

# 遍历列表
fruits="apple banana cherry date"
for fruit in $fruits; do
    echo "水果: $fruit"
done

# 遍历数字范围
echo -e "\n计数从 1 到 5:"
for i in {1..5}; do
    echo $i
done

# 遍历带步长的数字范围
echo -e "\n偶数从 2 到 10:"
for i in {2..10..2}; do
    echo $i
done

# C 风格的 for 循环
echo -e "\nC 风格循环:"
for ((i=1; i<=5; i++)); do
    echo $i
done
7.3.2 实际应用示例
bash 复制代码
#!/bin/bash
# 批量检查主机连通性

# 主机列表
hosts="192.168.1.1 192.168.1.10 192.168.1.20 www.baidu.com www.google.com"

# 检查每个主机
for host in $hosts; do
    echo -n "检查 $host: "
    # 发送 2 个 ICMP 包,超时时间 2 秒
    ping -c 2 -W 2 $host &>/dev/null
    if [ $? -eq 0 ]; then
        echo "连通"
    else
        echo "不通"
    fi
done

7.4 while 循环

while 循环在条件为真时重复执行命令:

bash 复制代码
#!/bin/bash

# 基本 while 循环
count=1
echo "计数到 5:"
while [ $count -le 5 ]; do
    echo $count
    count=$((count + 1))
    # 或者使用 let 命令: let count++
done

# 读取文件内容
echo -e "\n读取文件内容:"
file="test.txt"
# 创建测试文件
echo "第一行" > $file
echo "第二行" >> $file
echo "第三行" >> $file

# 使用 while 循环读取文件
while read line; do
    echo "行内容: $line"
done < $file

# 删除测试文件
rm -f $file

# 无限循环(按 Ctrl+C 退出)
echo -e "\n无限循环,按 Ctrl+C 退出"
count=1
while true; do
    echo "循环次数: $count"
    count=$((count + 1))
    sleep 1
    # 循环 5 次后退出
    if [ $count -gt 5 ]; then
        break
    fi
done

7.5 until 循环

until 循环与 while 循环相反,在条件为假时重复执行命令:

bash 复制代码
#!/bin/bash

# 基本 until 循环
count=1
echo "计数到 5:"
until [ $count -gt 5 ]; do
    echo $count
    let count++
done

# 实际应用:等待服务启动
echo -e "\n等待 httpd 服务启动..."
until systemctl is-active --quiet httpd; do
    echo "httpd 服务尚未启动,等待 3 秒..."
    sleep 3
done
echo "httpd 服务已启动"

7.6 循环控制语句

break 和 continue 用于控制循环流程:

bash 复制代码
#!/bin/bash

# break 示例:找到第一个偶数就退出
echo "寻找 1-10 中的第一个偶数:"
for num in {1..10}; do
    if [ $((num % 2)) -eq 0 ]; then
        echo "找到偶数: $num"
        break  # 退出循环
    fi
    echo "检查: $num"
done

# continue 示例:只显示奇数
echo -e "\n显示 1-10 中的奇数:"
for num in {1..10}; do
    if [ $((num % 2)) -eq 0 ]; then
        continue  # 跳过本次循环的剩余部分
    fi
    echo $num
done

# break N 和 continue N:控制多层循环
echo -e "\n多层循环控制:"
for i in {1..3}; do
    echo "外层循环: $i"
    for j in {1..3}; do
        if [ $j -eq 2 ]; then
            break 2  # 退出两层循环
            # continue 2  # 跳过外层循环的当前迭代
        fi
        echo "  内层循环: $j"
    done
done

八、函数与模块化

8.1 函数定义与调用

bash 复制代码
#!/bin/bash

# 基本函数定义
hello() {
    echo "Hello, World!"
}

# 调用函数
echo "调用 hello 函数:"
hello

# 带参数的函数
greet() {
    echo "Hello, $1!"
    echo "函数参数个数: $#"
    echo "所有参数: $*"
}

echo -e "\n调用 greet 函数:"
greet "John" "Doe"

# 带返回值的函数
add() {
    local sum=$(( $1 + $2 ))  # local 声明局部变量
    echo $sum  # 通过 echo 返回值
}

echo -e "\n调用 add 函数:"
result=$(add 10 20)
echo "10 + 20 = $result"

8.2 函数库与模块化

将常用函数放在单独的文件中,需要时引入使用:

  1. 创建函数库文件 myfuncs.sh
bash 复制代码
#!/bin/bash

# 显示错误信息
error() {
    echo "ERROR: $1" >&2
}

# 显示信息并退出
die() {
    error "$1"
    exit 1
}

# 检查命令是否存在
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# 计算文件大小总和
dir_size() {
    if [ $# -ne 1 ] || [ ! -d "$1" ]; then
        error "请提供一个目录作为参数"
        return 1
    fi
    du -sh "$1" | awk '{print $1}'
}
  1. 在脚本中引入函数库:
bash 复制代码
#!/bin/bash

# 引入函数库
source ./myfuncs.sh || die "无法加载函数库"

# 使用函数库中的函数
echo "检查 curl 命令是否存在:"
if command_exists curl; then
    echo "curl 已安装"
else
    error "curl 未安装"
fi

echo -e "\n计算当前目录大小:"
dir_size .

echo -e "\n测试错误处理:"
error "这是一个错误信息"
# die "这是一个致命错误,程序将退出"

九、文本处理三剑客

9.1 grep:文本搜索工具

bash 复制代码
#!/bin/bash

# 创建测试文件
test_file="grep_test.txt"
cat > $test_file << EOF
apple
Banana
cherry
date
elderberry
fig
Grape
kiwi
lemon
mango
EOF

echo "查找包含 'e' 的行:"
grep "e" $test_file

echo -e "\n忽略大小写查找包含 'a' 的行:"
grep -i "a" $test_file

echo -e "\n查找不包含 'e' 的行:"
grep -v "e" $test_file

echo -e "\n显示行号:"
grep -n "e" $test_file

echo -e "\n使用正则表达式查找以 'b' 或 'B' 开头的行:"
grep -i "^b" $test_file

# 清理测试文件
rm -f $test_file

9.2 sed:流编辑器

bash 复制代码
#!/bin/bash

# 创建测试文件
test_file="sed_test.txt"
cat > $test_file << EOF
apple
Banana
cherry
date
elderberry
fig
Grape
kiwi
lemon
mango
EOF

echo "原始文件内容:"
cat $test_file

echo -e "\n将 'e' 替换为 'E':"
sed 's/e/E/' $test_file

echo -e "\n全局替换 'e' 为 'E':"
sed 's/e/E/g' $test_file

echo -e "\n删除包含 'a' 的行:"
sed '/a/d' $test_file

echo -e "\n在行首添加序号:"
sed '=' $test_file | sed 'N;s/\n/. /'

echo -e "\n在文件中插入一行:"
sed '3i inserted line' $test_file

# 原地修改文件(备份原始文件)
sed -i.bak 's/^/fruit: /' $test_file
echo -e "\n原地修改后的文件:"
cat $test_file

# 清理测试文件
rm -f $test_file $test_file.bak

9.3 awk:文本处理语言

bash 复制代码
#!/bin/bash

# 创建测试文件
test_file="awk_test.txt"
cat > $test_file << EOF
Name    Age     City
Alice   25      New York
Bob     30      London
Charlie 35      Paris
David   40      Tokyo
EOF

echo "原始文件内容:"
cat $test_file

echo -e "\n打印第一列和第三列:"
awk '{print $1, $3}' $test_file

echo -e "\n跳过表头,打印年龄大于 30 的行:"
awk 'NR > 1 && $2 > 30 {print $1, "is", $2, "years old"}' $test_file

echo -e "\n计算平均年龄:"
awk 'NR > 1 {sum += $2; count++} END {print "平均年龄:", sum/count}' $test_file

echo -e "\n格式化输出:"
awk 'NR == 1 {printf "%-10s %-5s %-10s\n", $1, $2, $3} 
     NR > 1 {printf "%-10s %-5d %-10s\n", $1, $2, $3}' $test_file

# 清理测试文件
rm -f $test_file

十、企业级实战案例

10.1 系统监控脚本

bash 复制代码
#!/bin/bash
# 系统监控脚本,定期检查系统资源使用情况

# 配置
THRESHOLD_CPU=80    # CPU 使用率阈值(%)
THRESHOLD_MEM=80    # 内存使用率阈值(%)
THRESHOLD_DISK=80   # 磁盘使用率阈值(%)
LOG_FILE="/var/log/system_monitor.log"
CHECK_INTERVAL=60   # 检查间隔(秒)

# 日志记录函数
log() {
    local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    echo "[$timestamp] $1" >> $LOG_FILE
}

# 检查 CPU 使用率
check_cpu() {
    local cpu_usage=$(top -b -n 1 | grep "%Cpu(s)" | awk '{print $2 + $4}')
    local cpu_usage_int=$(printf "%.0f" $cpu_usage)
    
    if [ $cpu_usage_int -ge $THRESHOLD_CPU ]; then
        local message="CPU 使用率过高: $cpu_usage_int%"
        echo $message
        log $message
        # 可以在这里添加报警逻辑,如发送邮件
    else
        echo "CPU 使用率正常: $cpu_usage_int%"
    fi
}

# 检查内存使用率
check_memory() {
    local mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}')
    local mem_usage_int=$(printf "%.0f" $mem_usage)
    
    if [ $mem_usage_int -ge $THRESHOLD_MEM ]; then
        local message="内存使用率过高: $mem_usage_int%"
        echo $message
        log $message
        # 可以在这里添加报警逻辑,如发送邮件
    else
        echo "内存使用率正常: $mem_usage_int%"
    fi
}

# 检查磁盘使用率
check_disk() {
    local disk_usage=$(df -h / | tail -1 | awk '{print $5}' | sed 's/%//')
    
    if [ $disk_usage -ge $THRESHOLD_DISK ]; then
        local message="根目录磁盘使用率过高: $disk_usage%"
        echo $message
        log $message
        # 可以在这里添加报警逻辑,如发送邮件
    else
        echo "根目录磁盘使用率正常: $disk_usage%"
    fi
}

# 检查系统负载
check_load() {
    local load_avg=$(uptime | awk -F 'load average: ' '{print $2}' | cut -d',' -f1)
    local cpu_cores=$(grep -c ^processor /proc/cpuinfo)
    
    echo "系统负载平均值(1分钟): $load_avg (CPU核心数: $cpu_cores)"
}

# 主函数
main() {
    echo "=== 系统监控报告 $(date "+%Y-%m-%d %H:%M:%S") ==="
    check_cpu
    check_memory
    check_disk
    check_load
    echo "==========================================="
    echo
}

# 如果脚本带有参数 "once",则只运行一次
if [ "$1" = "once" ]; then
    main
else
    # 否则循环运行
    while true; do
        main
        sleep $CHECK_INTERVAL
    done
fi

10.2 日志分析脚本

bash 复制代码
#!/bin/bash
# Nginx 访问日志分析脚本

# 配置
LOG_FILE="/var/log/nginx/access.log"
TOP_N=10  # 显示前 N 条记录

# 检查日志文件是否存在
if [ ! -f "$LOG_FILE" ]; then
    echo "错误: 日志文件 $LOG_FILE 不存在"
    exit 1
fi

# 显示菜单
show_menu() {
    echo "Nginx 日志分析工具"
    echo "1. 访问量最高的 IP 地址"
    echo "2. 访问量最高的 URL"
    echo "3. 访问量最高的 HTTP 状态码"
    echo "4. 访问量最高的用户代理(UA)"
    echo "5. 按小时统计访问量"
    echo "6. 退出"
    echo -n "请选择(1-6): "
}

# 分析访问量最高的 IP
analyze_top_ips() {
    echo -e "\n访问量最高的 $TOP_N 个 IP 地址:"
    awk '{print $1}' $LOG_FILE | sort | uniq -c | sort -nr | head -n $TOP_N
}

# 分析访问量最高的 URL
analyze_top_urls() {
    echo -e "\n访问量最高的 $TOP_N 个 URL:"
    awk '{print $7}' $LOG_FILE | sort | uniq -c | sort -nr | head -n $TOP_N
}

# 分析访问量最高的状态码
analyze_top_status() {
    echo -e "\n访问量最高的 $TOP_N 个 HTTP 状态码:"
    awk '{print $9}' $LOG_FILE | sort | uniq -c | sort -nr | head -n $TOP_N
}

# 分析访问量最高的用户代理
analyze_top_ua() {
    echo -e "\n访问量最高的 $TOP_N 个用户代理:"
    awk -F'"' '{print $6}' $LOG_FILE | sort | uniq -c | sort -nr | head -n $TOP_N
}

# 按小时统计访问量
analyze_hourly() {
    echo -e "\n按小时统计访问量:"
    awk '{print substr($4, 14, 5)}' $LOG_FILE | sort | uniq -c | sort -n
}

# 主循环
while true; do
    show_menu
    read choice
    case $choice in
        1)
            analyze_top_ips
            ;;
        2)
            analyze_top_urls
            ;;
        3)
            analyze_top_status
            ;;
        4)
            analyze_top_ua
            ;;
        5)
            analyze_hourly
            ;;
        6)
            echo "退出程序"
            exit 0
            ;;
        *)
            echo "无效选择,请重新输入"
            ;;
    esac
    echo -e "\n按 Enter 键继续..."
    read
    clear
done

10.3 自动备份脚本

bash 复制代码
#!/bin/bash
# 自动备份脚本,支持文件和数据库备份

# 配置
BACKUP_DIR="/backup"               # 备份存储目录
SOURCE_DIRS="/etc /var/www"        # 需要备份的目录,用空格分隔
DB_NAME="mydatabase"               # 数据库名称
DB_USER="backupuser"               # 数据库用户名
DB_PASS="backuppassword"           # 数据库密码
RETENTION_DAYS=7                   # 备份保留天数
DATE=$(date +%Y%m%d_%H%M%S)        # 当前日期时间
BACKUP_FILE="$BACKUP_DIR/full_backup_$DATE.tar.gz"  # 备份文件名

# 确保备份目录存在
mkdir -p $BACKUP_DIR

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

# 检查依赖命令
check_dependencies() {
    local dependencies=("tar" "mysqldump" "gzip")
    for dep in "${dependencies[@]}"; do
        if ! command -v $dep >/dev/null 2>&1; then
            log "错误: 未找到命令 $dep,请安装后再运行"
            exit 1
        fi
    done
}

# 备份文件
backup_files() {
    log "开始备份文件..."
    local temp_dir=$(mktemp -d)
    
    # 复制要备份的目录
    for dir in $SOURCE_DIRS; do
        if [ -d "$dir" ]; then
            mkdir -p $temp_dir$(dirname $dir)
            cp -a $dir $temp_dir$dir
            log "已备份目录: $dir"
        else
            log "警告: 目录 $dir 不存在,跳过备份"
        fi
    done
    
    # 备份数据库
    log "开始备份数据库 $DB_NAME..."
    mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $temp_dir/${DB_NAME}_backup.sql
    if [ $? -eq 0 ]; then
        log "数据库备份成功"
    else
        log "警告: 数据库备份失败"
    fi
    
    # 打包备份文件
    log "正在打包备份文件..."
    tar -zcf $BACKUP_FILE -C $temp_dir .
    
    # 清理临时目录
    rm -rf $temp_dir
    
    if [ -f $BACKUP_FILE ]; then
        log "备份文件已创建: $BACKUP_FILE"
        log "备份大小: $(du -h $BACKUP_FILE | awk '{print $1}')"
        return 0
    else
        log "错误: 备份文件创建失败"
        return 1
    fi
}

# 清理旧备份
cleanup_old_backups() {
    log "开始清理 $RETENTION_DAYS 天前的旧备份..."
    find $BACKUP_DIR -name "full_backup_*.tar.gz" -type f -mtime +$RETENTION_DAYS -delete
    log "旧备份清理完成"
}

# 主函数
main() {
    log "===== 开始执行自动备份 ====="
    
    check_dependencies
    
    if backup_files; then
        cleanup_old_backups
        log "===== 自动备份执行完成 ====="
        exit 0
    else
        log "===== 自动备份执行失败 ====="
        exit 1
    fi
}

# 执行主函数
main

十一、Shell 脚本调试与优化

11.1 脚本调试方法

bash 复制代码
#!/bin/bash

# 调试方法 1: 使用 -x 选项执行脚本,显示执行的每一行命令
# bash -x script.sh

# 调试方法 2: 在脚本中使用 set -x 和 set +x 控制调试范围
# set -x  # 开始调试
# 要调试的代码
# set +x  # 结束调试

# 调试方法 3: 使用 -n 选项检查脚本语法错误
# bash -n script.sh

# 示例脚本
name="John"
age=30

echo "姓名: $name"
echo "年龄: $age"

if [ $age -ge 18 ]; then
    echo "已成年"
else
    echo "未成年"
fi

11.2 脚本优化技巧

  1. 减少子进程创建 :尽量使用 Shell 内置命令,减少外部命令调用

​ 2.优化循环 :避免在循环中执行耗时操作

  1. 使用管道替代临时文件

  2. 适当使用变量缓存结果 :避免重复计算

  3. 合并命令 **:减少进程创建开销

bash 复制代码
#!/bin/bash

# 优化前
start_time=$(date +%s)
for i in {1..1000}; do
    # 每次循环都创建新进程
    echo $i >> numbers.txt
done
end_time=$(date +%s)
echo "优化前耗时: $((end_time - start_time)) 秒"
rm numbers.txt

# 优化后
start_time=$(date +%s)
# 只创建一个进程
{
    for i in {1..1000}; do
        echo $i
    done
} >> numbers.txt
end_time=$(date +%s)
echo "优化后耗时: $((end_time - start_time)) 秒"
rm numbers.txt

十二、Shell 脚本最佳实践

1.** 脚本开头指定解释器 :#!/bin/bash

2.添加脚本描述信息 :包括功能、作者、版本等

3.检查脚本所需依赖 :确保必要的命令和工具存在

4.处理错误情况 :使用 set -e 自动退出错误,或显式检查错误

5.使用函数组织代码 :提高可读性和复用性

6.变量引用加引号 :防止空格等特殊字符引起的问题

7.使用局部变量 :函数内部变量使用 local 声明

8.避免使用硬编码 :配置参数集中定义,便于修改

9.添加注释 :解释复杂逻辑和关键步骤

  1. 进行边界测试 **:确保脚本在异常情况下的稳定性

十三、总结与进阶学习

Shell 脚本编程是 Linux 系统管理和自动化运维的重要技能,要进一步提升 Shell 脚本编程能力,可以:

  1. 阅读系统自带的 Shell 脚本(如 /etc/init.d/ 目录下的服务脚本)
  2. 学习更复杂的文本处理和正则表达式
  3. 掌握 Shell 脚本的调试和性能优化技巧
  4. 了解不同 Shell(如 zsh)的特性和扩展功能
相关推荐
bnsarocket7 小时前
Verilog和FPGA的自学笔记4——多路选择器(always语句)
笔记·fpga开发·编程·verilog·自学·硬件编程
程序员鱼皮1 天前
让老弟做个数据同步,结果踩了 7 个大坑!
java·后端·计算机·程序员·编程·职场
野猪疯驴2 天前
Linux shell学习(更新中....)
linux·shell
胡斌附体7 天前
springbatch使用记录
数据库·接口·shell·命令·批量·springbatch·并发抽取
NiKo_W8 天前
Linux 自定义shell命令解释器
linux·bash·shell
C++chaofan12 天前
通过Selenium实现网页截图来生成应用封面
java·spring boot·后端·selenium·测试工具·编程·截图
siriuuus12 天前
Shell编程:awk 数组及自定义函数实践
shell
小七mod13 天前
【BTC】比特币脚本
web3·区块链·脚本·比特币·btc
Just_Paranoid15 天前
【Gerrit Patch】批量下载 Gerrit 提交的 Patch
git·gerrit·shell·patch