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:
echo $SHELL
查看系统安装的所有 Shell:
cat /etc/shells
二、为什么要学习 Shell 脚本编程
2.1 自动化运维的核心工具
在 Linux 服务器管理中,许多重复性工作可以通过 Shell 脚本自动化完成:
- 系统监控与报警
- 日志分析与统计
- 批量部署与配置
- 定时任务执行
- 数据备份与恢复
2.2 处理文本文件的优势
Linux 系统中绝大多数配置文件、日志文件都是纯文本格式,Shell 脚本结合文本处理工具(grep、sed、awk 等)可以高效处理这些文件:
- 快速提取关键信息
- 批量修改配置参数
- 分析日志并生成报告
- 处理格式化数据
2.3 职业发展的必备技能
对于运维工程师、开发工程师、测试工程师而言,Shell 脚本编程是一项重要技能:
- 提升工作效率,减少重复劳动
- 实现复杂的系统管理任务
- 更好地理解 Linux 系统原理
- 为学习更复杂的编程语言打下基础
三、Shell 脚本入门准备
3.1 环境准备
学习 Shell 脚本编程不需要特殊的开发环境,只需要:
- 一台 Linux 系统的计算机或服务器
- 一个文本编辑器(推荐 vim 或 nano)
- 基本的 Linux 命令操作能力
3.2 vim 编辑器配置
vim 是编写 Shell 脚本的理想工具,以下是一些实用配置:
# 创建或编辑 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 脚本:
# 创建脚本文件
vim hello.sh# 输入以下内容
#!/bin/bash
# 这是一个简单的 Shell 脚本
echo "Hello, World!"
echo "当前时间: $(date)"
echo "当前用户: $USER"
echo "当前目录: $(pwd)"
脚本执行方法:
-
使用 bash 命令执行(不需要执行权限):bash hello.sh
-
赋予执行权限后直接执行:
# 添加执行权限
chmod +x hello.sh# 执行脚本
./hello.sh
- 使用绝对路径执行:
# 假设脚本在 /home/user 目录下
/home/user/hello.sh
- 使用 source 命令执行(在当前 Shell 环境中执行):
source hello.sh
# 或
. hello.sh
4.2 脚本结构解析
一个规范的 Shell 脚本通常包含以下几个部分:
- 解释器声明:第一行的
#!/bin/bash
指定脚本使用 bash 解释器 - 注释信息:以
#
开头的行,用于说明脚本功能、作者、版本等 - 脚本主体:具体的命令和控制结构
#!/bin/bash
# 脚本名称: system_info.sh
# 作 者: Your Name
# 日 期: 2023-07-01
# 版 本: 1.0
# 功 能: 显示系统基本信息echo "=== 系统信息 ==="
uname -aecho -e "\n=== CPU 信息 ==="
lscpu | grep 'Model name\|CPU(s)'echo -e "\n=== 内存信息 ==="
free -hecho -e "\n=== 磁盘信息 ==="
df -h
五、Shell 变量与数据类型
5.1 变量定义与使用
Shell 变量不需要声明类型,直接定义即可:
#!/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 系统环境变量
系统预设了一些环境变量,可以直接使用:
#!/bin/bashecho "当前用户: $USER"
echo "用户家目录: $HOME"
echo "当前工作目录: $PWD"
echo "命令搜索路径: $PATH"
echo "主机名: $HOSTNAME"
echo "Shell 类型: $SHELL"
echo "终端类型: $TERM"
echo "进程 ID: $$" # 当前脚本的进程 ID
echo "退出状态: $?" # 上一条命令的退出状态,0 表示成功
5.3 位置参数变量
用于获取脚本执行时传入的参数:
#!/bin/bashecho "脚本名称: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "第三个参数: $3"
echo "所有参数: $*" # 所有参数作为一个整体
echo "所有参数: $@" # 所有参数作为独立个体
echo "参数个数: $#" # 参数的数量
执行示例:
./params.sh first second third
5.4 变量操作
#!/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 算术运算符
#!/bin/basha=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 赋值运算符
#!/bin/basha=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 数值比较
#!/bin/basha=10
b=20# 使用 [ ] 进行比较,注意空格
if [ $a -eq $b ]; thenecho "$a 等于 $b"
elseecho "$a 不等于 $b"
fiif [ $a -gt $b ]; thenecho "$a 大于 $b"
elseecho "$a 不大于 $b"
fi# 常用数值比较运算符
# -eq: 等于
# -ne: 不等于
# -gt: 大于
# -lt: 小于
# -ge: 大于等于
# -le: 小于等于
6.3.2 字符串比较
#!/bin/bashstr1="hello"
str2="world"
str3="hello"if [ "$str1" = "$str2" ]; thenecho "$str1 等于 $str2"
elseecho "$str1 不等于 $str2"
fiif [ "$str1" = "$str3" ]; thenecho "$str1 等于 $str3"
elseecho "$str1 不等于 $str3"
fi# 字符串长度比较
if [ -z "$str1" ]; thenecho "$str1 是空字符串"
elseecho "$str1 不是空字符串,长度为 ${#str1}"
fi# 常用字符串比较运算符
# =: 等于
# !=: 不等于
# -z: 字符串长度为 0
# -n: 字符串长度不为 0
6.4 逻辑运算符
#!/bin/basha=10
b=20
c=30# 逻辑与 -a 或 &&
if [ $a -lt $b -a $b -lt $c ]; thenecho "$a < $b < $c 成立"
fiif [ $a -lt $b ] && [ $b -lt $c ]; thenecho "$a < $b < $c 成立"
fi# 逻辑或 -o 或 ||
if [ $a -gt $b -o $b -lt $c ]; thenecho "条件成立"
fiif [ $a -gt $b ] || [ $b -lt $c ]; thenecho "条件成立"
fi# 逻辑非 !
if [ ! $a -gt $b ]; thenecho "$a 不大于 $b"
fi
6.5 文件测试运算符
#!/bin/bashfile="test.txt"
dir="/tmp"# 检查文件是否存在
if [ -e "$file" ]; thenecho "$file 存在"
elseecho "$file 不存在"touch "$file"echo "已创建 $file"
fi# 检查是否为普通文件
if [ -f "$file" ]; thenecho "$file 是普通文件"
fi# 检查是否为目录
if [ -d "$dir" ]; thenecho "$dir 是目录"
fi# 检查文件是否可执行
if [ -x "$file" ]; thenecho "$file 可执行"
elseecho "$file 不可执行"chmod +x "$file"echo "已设置 $file 可执行权限"
fi# 常用文件测试运算符
# -e: 文件或目录存在
# -f: 是普通文件
# -d: 是目录
# -r: 可读
# -w: 可写
# -x: 可执行
# -s: 文件大小不为 0
# -L: 是符号链接
七、流程控制语句
7.1 if 条件语句
7.1.1 基本语法
#!/bin/bash# 单分支 if 语句
age=18
if [ $age -ge 18 ]; thenecho "已成年"
fi# 双分支 if 语句
score=75
if [ $score -ge 60 ]; thenecho "成绩合格"
elseecho "成绩不合格"
fi# 多分支 if 语句
if [ $score -ge 90 ]; thenecho "优秀"
elif [ $score -ge 80 ]; thenecho "良好"
elif [ $score -ge 60 ]; thenecho "及格"
elseecho "不及格"
fi
7.1.2 实际应用示例
#!/bin/bash
# 检查服务是否运行service_name="sshd"# 检查服务状态
if systemctl is-active --quiet $service_name; thenecho "$service_name 服务正在运行"# 检查服务是否设置为开机启动if systemctl is-enabled --quiet $service_name; thenecho "$service_name 服务已设置为开机启动"elseecho "$service_name 服务未设置为开机启动"read -p "是否设置 $service_name 为开机启动? (y/n) " choiceif [ "$choice" = "y" ] || [ "$choice" = "Y" ]; thensystemctl enable $service_nameecho "$service_name 已设置为开机启动"fifi
elseecho "$service_name 服务未运行"read -p "是否启动 $service_name 服务? (y/n) " choiceif [ "$choice" = "y" ] || [ "$choice" = "Y" ]; thensystemctl start $service_nameif [ $? -eq 0 ]; thenecho "$service_name 服务启动成功"elseecho "$service_name 服务启动失败"fifi
fi
7.2 case 语句
当需要匹配多个条件时,case 语句比 if 语句更简洁:
#!/bin/bash
# 服务管理脚本if [ $# -ne 1 ]; thenecho "用法: $0 [start|stop|restart|status]"exit 1
fiservice_name="httpd"case "$1" instart)systemctl start $service_nameecho "$service_name 服务已启动";;stop)systemctl stop $service_nameecho "$service_name 服务已停止";;restart)systemctl restart $service_nameecho "$service_name 服务已重启";;status)systemctl status $service_name;;*)echo "无效参数: $1"echo "用法: $0 [start|stop|restart|status]"exit 1;;
esac
7.3 for 循环
7.3.1 基本语法
#!/bin/bash# 遍历列表
fruits="apple banana cherry date"
for fruit in $fruits; doecho "水果: $fruit"
done# 遍历数字范围
echo -e "\n计数从 1 到 5:"
for i in {1..5}; doecho $i
done# 遍历带步长的数字范围
echo -e "\n偶数从 2 到 10:"
for i in {2..10..2}; doecho $i
done# C 风格的 for 循环
echo -e "\nC 风格循环:"
for ((i=1; i<=5; i++)); doecho $i
done
7.3.2 实际应用示例
#!/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; doecho -n "检查 $host: "# 发送 2 个 ICMP 包,超时时间 2 秒ping -c 2 -W 2 $host &>/dev/nullif [ $? -eq 0 ]; thenecho "连通"elseecho "不通"fi
done
7.4 while 循环
while 循环在条件为真时重复执行命令:
#!/bin/bash# 基本 while 循环
count=1
echo "计数到 5:"
while [ $count -le 5 ]; doecho $countcount=$((count + 1))# 或者使用 let 命令: let count++
done# 读取文件内容
echo -e "\n读取文件内容:"
file="test.txt"
# 创建测试文件
echo "第一行" > $file
echo "第二行" >> $file
echo "第三行" >> $file# 使用 while 循环读取文件
while read line; doecho "行内容: $line"
done < $file# 删除测试文件
rm -f $file# 无限循环(按 Ctrl+C 退出)
echo -e "\n无限循环,按 Ctrl+C 退出"
count=1
while true; doecho "循环次数: $count"count=$((count + 1))sleep 1# 循环 5 次后退出if [ $count -gt 5 ]; thenbreakfi
done
7.5 until 循环
until 循环与 while 循环相反,在条件为假时重复执行命令:
#!/bin/bash# 基本 until 循环
count=1
echo "计数到 5:"
until [ $count -gt 5 ]; doecho $countlet count++
done# 实际应用:等待服务启动
echo -e "\n等待 httpd 服务启动..."
until systemctl is-active --quiet httpd; doecho "httpd 服务尚未启动,等待 3 秒..."sleep 3
done
echo "httpd 服务已启动"
7.6 循环控制语句
break 和 continue 用于控制循环流程:
#!/bin/bash# break 示例:找到第一个偶数就退出
echo "寻找 1-10 中的第一个偶数:"
for num in {1..10}; doif [ $((num % 2)) -eq 0 ]; thenecho "找到偶数: $num"break # 退出循环fiecho "检查: $num"
done# continue 示例:只显示奇数
echo -e "\n显示 1-10 中的奇数:"
for num in {1..10}; doif [ $((num % 2)) -eq 0 ]; thencontinue # 跳过本次循环的剩余部分fiecho $num
done# break N 和 continue N:控制多层循环
echo -e "\n多层循环控制:"
for i in {1..3}; doecho "外层循环: $i"for j in {1..3}; doif [ $j -eq 2 ]; thenbreak 2 # 退出两层循环# continue 2 # 跳过外层循环的当前迭代fiecho " 内层循环: $j"done
done
八、函数与模块化
8.1 函数定义与调用
#!/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 函数库与模块化
将常用函数放在单独的文件中,需要时引入使用:
- 创建函数库文件
myfuncs.sh
:
#!/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" ]; thenerror "请提供一个目录作为参数"return 1fidu -sh "$1" | awk '{print $1}'
}
- 在脚本中引入函数库:
#!/bin/bash# 引入函数库
source ./myfuncs.sh || die "无法加载函数库"# 使用函数库中的函数
echo "检查 curl 命令是否存在:"
if command_exists curl; thenecho "curl 已安装"
elseerror "curl 未安装"
fiecho -e "\n计算当前目录大小:"
dir_size .echo -e "\n测试错误处理:"
error "这是一个错误信息"
# die "这是一个致命错误,程序将退出"
九、文本处理三剑客
9.1 grep:文本搜索工具
#!/bin/bash# 创建测试文件
test_file="grep_test.txt"
cat > $test_file << EOF
apple
Banana
cherry
date
elderberry
fig
Grape
kiwi
lemon
mango
EOFecho "查找包含 'e' 的行:"
grep "e" $test_fileecho -e "\n忽略大小写查找包含 'a' 的行:"
grep -i "a" $test_fileecho -e "\n查找不包含 'e' 的行:"
grep -v "e" $test_fileecho -e "\n显示行号:"
grep -n "e" $test_fileecho -e "\n使用正则表达式查找以 'b' 或 'B' 开头的行:"
grep -i "^b" $test_file# 清理测试文件
rm -f $test_file
9.2 sed:流编辑器
#!/bin/bash# 创建测试文件
test_file="sed_test.txt"
cat > $test_file << EOF
apple
Banana
cherry
date
elderberry
fig
Grape
kiwi
lemon
mango
EOFecho "原始文件内容:"
cat $test_fileecho -e "\n将 'e' 替换为 'E':"
sed 's/e/E/' $test_fileecho -e "\n全局替换 'e' 为 'E':"
sed 's/e/E/g' $test_fileecho -e "\n删除包含 'a' 的行:"
sed '/a/d' $test_fileecho -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:文本处理语言
#!/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
EOFecho "原始文件内容:"
cat $test_fileecho -e "\n打印第一列和第三列:"
awk '{print $1, $3}' $test_fileecho -e "\n跳过表头,打印年龄大于 30 的行:"
awk 'NR > 1 && $2 > 30 {print $1, "is", $2, "years old"}' $test_fileecho -e "\n计算平均年龄:"
awk 'NR > 1 {sum += $2; count++} END {print "平均年龄:", sum/count}' $test_fileecho -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 系统监控脚本
#!/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 ]; thenlocal message="CPU 使用率过高: $cpu_usage_int%"echo $messagelog $message# 可以在这里添加报警逻辑,如发送邮件elseecho "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 ]; thenlocal message="内存使用率过高: $mem_usage_int%"echo $messagelog $message# 可以在这里添加报警逻辑,如发送邮件elseecho "内存使用率正常: $mem_usage_int%"fi
}# 检查磁盘使用率
check_disk() {local disk_usage=$(df -h / | tail -1 | awk '{print $5}' | sed 's/%//')if [ $disk_usage -ge $THRESHOLD_DISK ]; thenlocal message="根目录磁盘使用率过高: $disk_usage%"echo $messagelog $message# 可以在这里添加报警逻辑,如发送邮件elseecho "根目录磁盘使用率正常: $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_cpucheck_memorycheck_diskcheck_loadecho "==========================================="echo
}# 如果脚本带有参数 "once",则只运行一次
if [ "$1" = "once" ]; thenmain
else# 否则循环运行while true; domainsleep $CHECK_INTERVALdone
fi
10.2 日志分析脚本
#!/bin/bash
# Nginx 访问日志分析脚本# 配置
LOG_FILE="/var/log/nginx/access.log"
TOP_N=10 # 显示前 N 条记录# 检查日志文件是否存在
if [ ! -f "$LOG_FILE" ]; thenecho "错误: 日志文件 $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; doshow_menuread choicecase $choice in1)analyze_top_ips;;2)analyze_top_urls;;3)analyze_top_status;;4)analyze_top_ua;;5)analyze_hourly;;6)echo "退出程序"exit 0;;*)echo "无效选择,请重新输入";;esacecho -e "\n按 Enter 键继续..."readclear
done
10.3 自动备份脚本
#!/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[@]}"; doif ! command -v $dep >/dev/null 2>&1; thenlog "错误: 未找到命令 $dep,请安装后再运行"exit 1fidone
}# 备份文件
backup_files() {log "开始备份文件..."local temp_dir=$(mktemp -d)# 复制要备份的目录for dir in $SOURCE_DIRS; doif [ -d "$dir" ]; thenmkdir -p $temp_dir$(dirname $dir)cp -a $dir $temp_dir$dirlog "已备份目录: $dir"elselog "警告: 目录 $dir 不存在,跳过备份"fidone# 备份数据库log "开始备份数据库 $DB_NAME..."mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $temp_dir/${DB_NAME}_backup.sqlif [ $? -eq 0 ]; thenlog "数据库备份成功"elselog "警告: 数据库备份失败"fi# 打包备份文件log "正在打包备份文件..."tar -zcf $BACKUP_FILE -C $temp_dir .# 清理临时目录rm -rf $temp_dirif [ -f $BACKUP_FILE ]; thenlog "备份文件已创建: $BACKUP_FILE"log "备份大小: $(du -h $BACKUP_FILE | awk '{print $1}')"return 0elselog "错误: 备份文件创建失败"return 1fi
}# 清理旧备份
cleanup_old_backups() {log "开始清理 $RETENTION_DAYS 天前的旧备份..."find $BACKUP_DIR -name "full_backup_*.tar.gz" -type f -mtime +$RETENTION_DAYS -deletelog "旧备份清理完成"
}# 主函数
main() {log "===== 开始执行自动备份 ====="check_dependenciesif backup_files; thencleanup_old_backupslog "===== 自动备份执行完成 ====="exit 0elselog "===== 自动备份执行失败 ====="exit 1fi
}# 执行主函数
main
十一、Shell 脚本调试与优化
11.1 脚本调试方法
#!/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=30echo "姓名: $name"
echo "年龄: $age"if [ $age -ge 18 ]; thenecho "已成年"
elseecho "未成年"
fi
11.2 脚本优化技巧
- 减少子进程创建 :尽量使用 Shell 内置命令,减少外部命令调用
2.优化循环 :避免在循环中执行耗时操作
-
使用管道替代临时文件
-
适当使用变量缓存结果 :避免重复计算
-
合并命令 **:减少进程创建开销
#!/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}; doecho $idone
} >> 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.添加注释 :解释复杂逻辑和关键步骤
- 进行边界测试 **:确保脚本在异常情况下的稳定性
十三、总结与进阶学习
Shell 脚本编程是 Linux 系统管理和自动化运维的重要技能,要进一步提升 Shell 脚本编程能力,可以:
- 阅读系统自带的 Shell 脚本(如
/etc/init.d/
目录下的服务脚本) - 学习更复杂的文本处理和正则表达式
- 掌握 Shell 脚本的调试和性能优化技巧
- 了解不同 Shell(如 zsh)的特性和扩展功能