三天内速学liunx shell 脚 本 快速过一遍
三天内解决 ——快速学习 Linux Shell 文档
liunx shell 通常我们想定时上传文件,或下载,或清理日志,统一执行,也可以执行一些简单任务,
需要人不在线场让电脑自动操作电脑的时候,
真正写代码的时候以前都 是抄百度,不过现在有ai 了 ,更方便,不过还是建议我们提前快速学一遍、
适用对象:零基础到进阶开发者,对liunx 不熟悉的朋友
学习目标:读完本文档,能独立编写简单的脚本,能运行起来就好,因为后期我们还有更好的工具
阅读建议:每章先通读概念,再动手敲一遍代码,最后对照「本章小结」复盘
第一章:Shell 基础入门
1.1 什么是 Shell?什么时候用它?
Shell 是用户与 Linux 内核之间的"翻译官"。你输入的是人类能看懂的命令,Shell 把它翻译成内核能执行的操作。
什么时候写 Shell 脚本?
-
需要把多个命令组合在一起,一键执行
-
需要定时自动执行任务(如每天凌晨备份数据库)
-
需要批量处理文件(如批量重命名 1000 张图片)
-
需要快速搭建自动化部署流程
学习路径:
plain
看懂命令 → 写简单脚本 → 加判断逻辑 → 写函数复用 → 处理文本 → 实战项目
1.2 第一个 Shell 脚本:Hello World
目标:创建一个文件,运行后输出 "Hello, Shell!"
分几步:
-
创建脚本文件
-
第一行声明用哪个 Shell 解释器
-
写执行代码
-
给文件加执行权限
-
运行脚本
bash
#!/bin/bash
# 上面这行叫 "Shebang",告诉系统用 /bin/bash 来执行这个脚本
# # 开头的是注释,程序不会执行
echo "Hello, Shell!"
echo "当前时间是: $(date)"
echo "当前用户是: $USER"
运行方式:
bash
# 方式一:给执行权限后直接运行(推荐)
chmod +x hello.sh
./hello.sh
# 方式二:用 bash 命令直接解释执行(不需要 chmod)
bash hello.sh
1.3 变量:脚本的数据容器
目标:学会定义变量、使用变量、理解特殊变量
分几步:
-
定义变量(等号两边不能有空格!)
-
使用变量(前面加
$) -
了解只读变量和删除变量
-
掌握常用的特殊变量
bash
#!/bin/bash
# ========== 自定义变量 ==========
name="张三" # 字符串变量
age=25 # 数字变量(Shell 里其实全是字符串)
pi=3.14159
# 使用变量:前面加 $,建议用 ${} 包裹,防止歧义
echo "姓名: $name"
echo "年龄: ${age}岁"
# ========== 只读变量 ==========
readonly MAX_RETRY=3
# MAX_RETRY=5 # 这行会报错,因为只读变量不能修改
# ========== 删除变量 ==========
temp="临时数据"
unset temp
# echo $temp # 输出空,因为变量已被删除
# ========== 特殊变量(超级重要) ==========
echo "脚本名称: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "参数总数: $#"
echo "所有参数: $*"
echo "脚本进程号: $$"
echo "上一条命令执行结果: $?" # 0 表示成功,非0表示失败
运行测试:
bash
./test.sh hello world 123
1.4 输入与输出
目标:学会与用户交互,读写文件
bash
#!/bin/bash
# ========== 输出 ==========
echo "普通输出"
echo -e "带颜色输出: \033[31m红色文字\033[0m" # -e 启用转义
# ========== 输入 ==========
read -p "请输入你的名字: " username
echo "你好, $username!"
read -s -p "请输入密码(隐藏输入): " password # -s 隐藏输入
echo "" # 换行
echo "密码长度: ${#password}"
# ========== 重定向 ==========
echo "这是日志" > log.txt # > 覆盖写入
echo "追加内容" >> log.txt # >> 追加写入
ls /notexist 2> error.log # 2> 把错误信息写入文件
# ========== 命令替换 ==========
# 方式一:反引号(旧写法,不推荐)
current_dir=`pwd`
# 方式二:$()(新写法,推荐,支持嵌套)
current_dir=$(pwd)
echo "当前目录: $current_dir"
files=$(ls /home)
echo "文件列表: $files"
📌 第一章小结
表格
| 知识点 | 关键记忆 |
|---|---|
| Shebang | #!/bin/bash 必须放第一行 |
| 变量定义 | name="值",等号两侧不能有空格 |
| 变量使用 | $name 或 ${name} |
| 特殊变量 | $0 $1 $# $* $? $$ 必背 |
| 输入 | read -p "提示: " 变量名 |
| 输出 | echo "内容",> 覆盖,>> 追加 |
| 命令替换 | $(命令) |
第二章:流程控制
2.1 条件判断 if 语句
目标:让脚本根据不同情况执行不同代码
分几步:
-
理解判断语法
[ 条件 ](方括号两侧必须有空格!) -
学会数字比较、字符串比较、文件判断
-
掌握 if / elif / else 结构
bash
#!/bin/bash
read -p "请输入一个数字: " num
# ========== 数字比较 ==========
# -eq 等于 -ne 不等于 -gt 大于 -ge 大于等于 -lt 小于 -le 小于等于
if [ $num -gt 10 ]; then
echo "$num 大于 10"
elif [ $num -eq 10 ]; then
echo "$num 等于 10"
else
echo "$num 小于 10"
fi
# ========== 字符串比较 ==========
# = 等于 != 不等于 -z 长度为0 -n 长度非0
name="admin"
if [ "$name" = "admin" ]; then
echo "管理员身份确认"
fi
# ========== 文件判断 ==========
# -e 文件存在 -f 是普通文件 -d 是目录 -r 可读 -w 可写 -x 可执行
if [ -f "log.txt" ]; then
echo "log.txt 存在"
else
echo "log.txt 不存在,正在创建..."
touch log.txt
fi
# ========== 逻辑组合 ==========
# -a 表示 AND,-o 表示 OR
age=25
if [ $age -ge 18 -a $age -le 60 ]; then
echo "属于劳动年龄"
fi
# 或者用 [[ ]] 支持更现代的写法(推荐)
if [[ $age -ge 18 && $age -le 60 ]]; then
echo "属于劳动年龄(现代写法)"
fi
2.2 循环结构
目标:批量处理数据,自动化重复任务
for 循环
bash
#!/bin/bash
# ========== 方式一:遍历列表 ==========
echo "=== 遍历数字 ==="
for i in 1 2 3 4 5; do
echo "当前数字: $i"
done
# ========== 方式二:遍历范围 ==========
echo "=== 遍历范围 ==="
for i in {1..5}; do
echo "第 $i 次循环"
done
# ========== 方式三:C语言风格 ==========
echo "=== C风格循环 ==="
for ((i=0; i<5; i++)); do
echo "i = $i"
done
# ========== 方式四:遍历命令结果 ==========
echo "=== 遍历当前目录的 .sh 文件 ==="
for file in $(ls *.sh 2>/dev/null); do
echo "发现脚本: $file"
done
# ========== 实战:批量重命名 ==========
# 把当前目录所有 .txt 改成 .bak
for file in *.txt; do
[ -f "$file" ] || continue # 如果没有匹配文件,跳过
newname="${file%.txt}.bak" # 去掉 .txt 后缀,加上 .bak
mv "$file" "$newname"
echo "已重命名: $file -> $newname"
done
while 循环
bash
#!/bin/bash
# ========== 基本 while ==========
count=1
while [ $count -le 5 ]; do
echo "计数: $count"
count=$((count + 1)) # 或者 ((count++))
done
# ========== 读取文件逐行处理 ==========
# 场景:逐行读取配置文件,处理每一行
while read line; do
echo "处理: $line"
done < log.txt
# ========== 无限循环 + 条件退出 ==========
while true; do
read -p "输入 'exit' 退出: " input
if [ "$input" = "exit" ]; then
echo "程序结束"
break # 跳出循环
fi
echo "你输入了: $input"
done
until 循环(条件为假时执行)
bash
#!/bin/bash
# until 和 while 相反:条件为假时执行,为真时停止
count=1
until [ $count -gt 5 ]; do
echo "count = $count"
((count++))
done
2.3 case 多分支选择
目标:替代多个 if-elif,代码更清晰
bash
#!/bin/bash
read -p "请输入操作 [start|stop|restart|status]: " action
case $action in
start)
echo "正在启动服务..."
# 启动命令写这里
;;
stop)
echo "正在停止服务..."
# 停止命令写这里
;;
restart)
echo "正在重启服务..."
# 重启命令写这里
;;
status)
echo "服务状态: 运行中"
;;
*) # * 匹配所有其他情况,类似 default
echo "未知操作: $action"
echo "用法: $0 {start|stop|restart|status}"
exit 1
;;
esac
📌 第二章小结
表格
| 知识点 | 关键记忆 |
|---|---|
| if 判断 | [ 条件 ] 两侧必须有空格 |
| 数字比较 | -eq -ne -gt -ge -lt -le |
| 字符串比较 | = != -z -n |
| 文件判断 | -e -f -d -r -w -x |
| for 循环 | for i in 列表 或 for ((;;)) |
| while 循环 | while [ 条件 ]; do ... done |
| case | case $var in pattern) ... ;; esac |
| 跳出循环 | break 跳出,continue 跳过本次 |
第三章:函数与数组
3.1 函数:代码复用的利器
目标:把重复代码封装成函数,提高可维护性
分几步:
-
定义函数
-
调用函数
-
传递参数
-
返回值处理
bash
#!/bin/bash
# ========== 定义函数 ==========
# 方式一
function say_hello() {
echo "你好, $1!" # $1 是传入的第一个参数
echo "今天天气: $2"
}
# 方式二(不带 function 关键字)
check_file() {
local filepath=$1 # local 表示局部变量,只在函数内有效
if [ -f "$filepath" ]; then
echo "文件存在"
return 0 # return 返回数字状态码,0表示成功
else
echo "文件不存在"
return 1 # 非0表示失败
fi
}
# ========== 调用函数 ==========
say_hello "张三" "晴朗"
# ========== 获取函数返回值 ==========
check_file "test.txt"
result=$? # $? 获取上一条命令/函数的返回状态码
if [ $result -eq 0 ]; then
echo "检查通过"
else
echo "检查失败"
fi
# ========== 函数返回字符串(用 echo + 命令替换) ==========
get_timestamp() {
echo "$(date '+%Y-%m-%d %H:%M:%S')"
}
ts=$(get_timestamp)
echo "当前时间戳: $ts"
# ========== 实战:日志函数 ==========
log() {
local level=$1 # INFO/WARN/ERROR
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message"
}
log "INFO" "系统启动"
log "ERROR" "数据库连接失败"
3.2 数组:批量存储数据
目标:用数组存储多个值,批量处理
bash
#!/bin/bash
# ========== 定义数组 ==========
fruits=("苹果" "香蕉" "橙子" "葡萄")
# ========== 访问元素 ==========
echo "第一个水果: ${fruits[0]}" # 苹果
echo "第二个水果: ${fruits[1]}" # 香蕉
# ========== 数组操作 ==========
echo "数组长度: ${#fruits[@]}" # 4
echo "所有元素: ${fruits[@]}" # 苹果 香蕉 橙子 葡萄
echo "所有索引: ${!fruits[@]}" # 0 1 2 3
# ========== 遍历数组 ==========
echo "=== 遍历数组 ==="
for fruit in "${fruits[@]}"; do
echo "水果: $fruit"
done
# ========== 带索引遍历 ==========
echo "=== 带索引遍历 ==="
for i in "${!fruits[@]}"; do
echo "索引 $i: ${fruits[$i]}"
done
# ========== 添加/删除元素 ==========
fruits[4]="西瓜" # 添加
fruits+=("芒果") # 追加
unset fruits[1] # 删除索引1的元素
echo "修改后: ${fruits[@]}"
# ========== 关联数组(键值对,需要 bash 4.0+)==========
declare -A user_info # -A 声明关联数组
user_info["name"]="张三"
user_info["age"]="25"
user_info["city"]="深圳"
echo "用户名: ${user_info["name"]}"
echo "所有键: ${!user_info[@]}"
echo "所有值: ${user_info[@]}"
📌 第三章小结
表格
| 知识点 | 关键记忆 |
|---|---|
| 函数定义 | function name() { ... } 或 name() { ... } |
| 函数参数 | 函数内用 $1 $2 接收,类似脚本参数 |
| 局部变量 | local var=value |
| 返回状态 | return 0/1,用 $? 接收 |
| 返回字符串 | 函数内 echo,调用处 $(函数名) |
| 普通数组 | arr=(a b c),${arr[0]},${#arr[@]} |
| 关联数组 | declare -A arr,arr["key"]="value" |
第四章:文本处理三剑客
4.1 grep:文本搜索
目标:从大量文本中快速找到想要的内容
bash
#!/bin/bash
# ========== 基础搜索 ==========
grep "error" log.txt # 在文件中搜索包含 "error" 的行
grep -i "error" log.txt # -i 忽略大小写
grep -v "error" log.txt # -v 反向匹配,显示不包含 "error" 的行
grep -n "error" log.txt # -n 显示行号
# ========== 正则匹配 ==========
grep "^2024" log.txt # ^ 开头:以 2024 开头的行
grep "error$" log.txt # $ 结尾:以 error 结尾的行
grep "err[oa]r" log.txt # [] 匹配其中一个字符:error 或 errar
grep "err.*r" log.txt # .* 匹配任意字符
# ========== 在脚本中使用 grep ==========
# 场景:检查进程是否在运行
if pgrep nginx > /dev/null; then
echo "Nginx 正在运行"
else
echo "Nginx 未运行"
fi
# 场景:统计错误数量
error_count=$(grep -c "ERROR" app.log)
echo "错误数量: $error_count"
4.2 sed:流编辑器
目标:不打开文件,直接对文本进行增删改查
bash
#!/bin/bash
# ========== 替换 ==========
# sed 's/旧内容/新内容/' 文件
sed 's/apple/orange/' fruits.txt # 每行第一个 apple 替换为 orange
sed 's/apple/orange/g' fruits.txt # g 表示全局替换,一行内所有 apple
sed 's/apple/orange/2' fruits.txt # 只替换每行第2个
# ========== 直接修改文件(-i)==========
sed -i 's/localhost/127.0.0.1/g' config.txt
# ========== 删除行 ==========
sed '3d' file.txt # 删除第3行
sed '/^#/d' file.txt # 删除以 # 开头的行(注释)
sed '/^$/d' file.txt # 删除空行
# ========== 在脚本中使用 sed ==========
# 场景:修改配置文件中的端口号
config_file="server.conf"
old_port="8080"
new_port="9090"
sed -i "s/port=${old_port}/port=${new_port}/g" "$config_file"
echo "端口已修改为 $new_port"
# 场景:提取特定行
sed -n '5,10p' log.txt # 只显示第5到10行
4.3 awk:文本分析神器
目标:按列处理数据,做统计计算
bash
#!/bin/bash
# 假设有一个文件 data.txt,内容如下:
# 张三 25 8000
# 李四 30 12000
# 王五 28 9500
# ========== 基础用法 ==========
awk '{print $1}' data.txt # 打印第一列(姓名)
awk '{print $1, $3}' data.txt # 打印第一列和第三列
awk '{print "姓名:", $1, "工资:", $3}' data.txt
# ========== 带条件过滤 ==========
awk '$2 > 26 {print $1}' data.txt # 年龄大于26的姓名
awk '$3 >= 10000 {print $1, $3}' data.txt # 工资大于等于10000的
# ========== 计算 ==========
awk '{sum += $3} END {print "总工资:", sum}' data.txt
awk '{sum += $3; count++} END {print "平均工资:", sum/count}' data.txt
# ========== 指定分隔符 ==========
awk -F: '{print $1}' /etc/passwd # 以 : 为分隔符,打印用户名
# ========== 在脚本中使用 awk ==========
# 场景:获取本机 IP
ip=$(ip addr show eth0 | awk '/inet / {print $2}' | awk -F/ '{print $1}')
echo "本机IP: $ip"
# 场景:分析日志,统计每个 IP 访问次数
awk '{count[$1]++} END {for(ip in count) print ip, count[ip]}' access.log
📌 第四章小结
表格
| 命令 | 核心用途 | 关键参数 |
|---|---|---|
| grep | 搜索文本 | -i忽略大小写, -v反向, -n行号, -c计数 |
| sed | 替换/删除 | s/旧/新/g, d删除, -i直接修改 |
| awk | 按列处理/计算 | {print $1}, -F:指定分隔符, END{}收尾处理 |
第五章:高级特性
5.1 正则表达式
目标:用模式匹配复杂的文本规则
bash
#!/bin/bash
# ========== 基础元字符 ==========
# . 匹配任意一个字符
# * 匹配前一个字符0次或多次
# + 匹配前一个字符1次或多次(grep 用 -E)
# ? 匹配前一个字符0次或1次
# ^ 开头
# $ 结尾
# [] 匹配括号内任意一个字符
# [^] 匹配不在括号内的字符
# {} 匹配次数,如 {3} 正好3次,{2,5} 2到5次
# ========== 实战示例 ==========
# 验证邮箱格式
email="test@example.com"
if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "邮箱格式正确"
else
echo "邮箱格式错误"
fi
# 验证手机号(中国大陆)
phone="13800138000"
if [[ $phone =~ ^1[3-9][0-9]{9}$ ]]; then
echo "手机号格式正确"
else
echo "手机号格式错误"
fi
# 提取日志中的 IP 地址
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log
5.2 进程管理与后台任务
bash
#!/bin/bash
# ========== 后台运行 ==========
./long_task.sh & # & 放到后台运行
nohup ./task.sh & # nohup 用户退出后继续运行
# ========== 作业控制 ==========
jobs # 查看后台任务
fg %1 # 把后台任务1调到前台
bg %1 # 把暂停的任务1放到后台继续
kill %1 # 结束后台任务1
# ========== 等待进程 ==========
./task1.sh &
pid1=$!
./task2.sh &
pid2=$!
wait $pid1 # 等待 task1 完成
echo "任务1完成"
wait # 等待所有后台任务完成
echo "所有任务完成"
5.3 信号处理
目标:让脚本优雅地处理中断(如 Ctrl+C)
bash
#!/bin/bash
# ========== 定义信号处理函数 ==========
cleanup() {
echo ""
echo "收到中断信号,正在清理..."
# 删除临时文件
rm -f /tmp/my_script_*.tmp
echo "清理完成,退出"
exit 0
}
# ========== 注册信号处理器 ==========
trap cleanup INT TERM EXIT
# INT = Ctrl+C
# TERM = kill 命令
# EXIT = 脚本正常退出时也会执行
# ========== 主程序 ==========
echo "脚本运行中,按 Ctrl+C 测试信号处理..."
for i in {1..100}; do
echo "工作中... $i"
sleep 1
done
5.4 文件描述符与重定向进阶
bash
#!/bin/bash
# ========== 标准文件描述符 ==========
# 0 = 标准输入 (stdin)
# 1 = 标准输出 (stdout)
# 2 = 标准错误 (stderr)
# ========== 同时重定向输出和错误 ==========
command > output.log 2>&1 # 输出和错误都写入文件
command &> output.log # 同上(bash 4.0+)
# ========== 丢弃输出 ==========
command > /dev/null 2>&1 # 静默执行,什么都不显示
# ========== 自定义文件描述符 ==========
exec 3<> data.txt # 打开文件,3 是文件描述符
echo "写入数据" >&3
exec 3<&- # 关闭文件描述符
📌 第五章小结
表格
| 知识点 | 关键记忆 |
|---|---|
| 正则匹配 | [[ $var =~ 正则 ]] |
| 后台运行 | 命令 &,nohup 命令 & |
| 信号捕获 | trap '命令' 信号 |
| 重定向 | > 文件 2>&1,/dev/null |
第六章:实战项目
项目一:日志分析脚本
需求:分析 Nginx 访问日志,输出访问统计报告
bash
#!/bin/bash
LOG_FILE="${1:-/var/log/nginx/access.log}" # 默认日志路径
REPORT_FILE="report_$(date +%Y%m%d).txt"
if [ ! -f "$LOG_FILE" ]; then
echo "错误: 日志文件 $LOG_FILE 不存在"
exit 1
fi
echo "========== 访问日志分析报告 ==========" > "$REPORT_FILE"
echo "分析时间: $(date)" >> "$REPORT_FILE"
echo "日志文件: $LOG_FILE" >> "$REPORT_FILE"
echo "" >> "$REPORT_FILE"
# 总请求数
total=$(wc -l < "$LOG_FILE")
echo "总请求数: $total" >> "$REPORT_FILE"
# 独立 IP 数
unique_ips=$(awk '{print $1}' "$LOG_FILE" | sort -u | wc -l)
echo "独立IP数: $unique_ips" >> "$REPORT_FILE"
# 请求最多的 10 个 IP
echo "" >> "$REPORT_FILE"
echo "TOP 10 IP:" >> "$REPORT_FILE"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10 >> "$REPORT_FILE"
# 请求最多的 10 个 URL
echo "" >> "$REPORT_FILE"
echo "TOP 10 URL:" >> "$REPORT_FILE"
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10 >> "$REPORT_FILE"
# 状态码统计
echo "" >> "$REPORT_FILE"
echo "状态码分布:" >> "$REPORT_FILE"
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -rn >> "$REPORT_FILE"
echo "报告已生成: $REPORT_FILE"
cat "$REPORT_FILE"
项目二:系统监控脚本
需求:监控 CPU、内存、磁盘,超过阈值时报警
bash
#!/bin/bash
# ========== 配置阈值 ==========
CPU_THRESHOLD=80 # CPU 使用率阈值
MEM_THRESHOLD=80 # 内存使用率阈值
DISK_THRESHOLD=90 # 磁盘使用率阈值
LOG_FILE="/var/log/system_monitor.log"
# ========== 日志函数 ==========
log_msg() {
local level=$1
local msg=$2
local ts=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$ts] [$level] $msg" | tee -a "$LOG_FILE"
}
# ========== 检查 CPU ==========
check_cpu() {
# 取 1 分钟平均负载,与 CPU 核心数比较
local load=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | tr -d ',')
local cores=$(nproc)
local usage=$(echo "scale=2; $load * 100 / $cores" | bc)
if (( $(echo "$usage > $CPU_THRESHOLD" | bc -l) )); then
log_msg "WARN" "CPU 使用率过高: ${usage}% (阈值: ${CPU_THRESHOLD}%)"
else
log_msg "INFO" "CPU 正常: ${usage}%"
fi
}
# ========== 检查内存 ==========
check_memory() {
local mem_info=$(free | grep Mem)
local total=$(echo $mem_info | awk '{print $2}')
local used=$(echo $mem_info | awk '{print $3}')
local usage=$(echo "scale=2; $used * 100 / $total" | bc)
if (( $(echo "$usage > $MEM_THRESHOLD" | bc -l) )); then
log_msg "WARN" "内存使用率过高: ${usage}% (阈值: ${MEM_THRESHOLD}%)"
else
log_msg "INFO" "内存正常: ${usage}%"
fi
}
# ========== 检查磁盘 ==========
check_disk() {
df -h | awk 'NR>1 {
gsub(/%/,"",$5)
if ($5 > '"$DISK_THRESHOLD"') {
print "WARN|分区 " $6 " 使用率: " $5 "%"
}
}' | while IFS='|' read level msg; do
log_msg "$level" "$msg"
done
}
# ========== 主程序 ==========
log_msg "INFO" "========== 开始系统监控 =========="
check_cpu
check_memory
check_disk
log_msg "INFO" "========== 监控结束 =========="
项目三:自动化部署脚本
需求:一键部署 Web 应用,包含备份、更新、回滚
bash
#!/bin/bash
# ========== 配置 ==========
APP_NAME="myweb"
APP_DIR="/var/www/$APP_NAME"
BACKUP_DIR="/backup/$APP_NAME"
GIT_REPO="https://github.com/user/myweb.git"
BRANCH="main"
# ========== 颜色输出 ==========
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
NC='\033[0m'
log() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
# ========== 备份 ==========
backup() {
local backup_name="backup_$(date +%Y%m%d_%H%M%S).tar.gz"
mkdir -p "$BACKUP_DIR"
if [ -d "$APP_DIR" ]; then
tar -czf "$BACKUP_DIR/$backup_name" -C "$(dirname $APP_DIR)" "$(basename $APP_DIR)"
log "备份完成: $backup_name"
else
warn "应用目录不存在,跳过备份"
fi
}
# ========== 部署 ==========
deploy() {
log "开始部署 $APP_NAME ..."
# 1. 备份
backup
# 2. 拉取代码
if [ -d "$APP_DIR/.git" ]; then
cd "$APP_DIR" || error "进入目录失败"
git pull origin "$BRANCH" || error "拉取代码失败"
else
git clone -b "$BRANCH" "$GIT_REPO" "$APP_DIR" || error "克隆代码失败"
fi
# 3. 安装依赖(示例:Node.js 项目)
cd "$APP_DIR" || error "进入目录失败"
if [ -f "package.json" ]; then
npm install || error "安装依赖失败"
npm run build || error "构建失败"
fi
# 4. 重启服务
if systemctl is-active --quiet "$APP_NAME"; then
systemctl restart "$APP_NAME" || error "重启服务失败"
else
warn "服务 $APP_NAME 未运行,请手动启动"
fi
log "部署完成!"
}
# ========== 回滚 ==========
rollback() {
local latest_backup=$(ls -t "$BACKUP_DIR"/*.tar.gz 2>/dev/null | head -1)
if [ -z "$latest_backup" ]; then
error "没有找到备份文件"
fi
log "开始回滚,使用备份: $(basename $latest_backup)"
# 停止服务
systemctl stop "$APP_NAME" 2>/dev/null
# 恢复备份
rm -rf "$APP_DIR"
tar -xzf "$latest_backup" -C "$(dirname $APP_DIR)"
# 启动服务
systemctl start "$APP_NAME"
log "回滚完成!"
}
# ========== 主入口 ==========
case "${1:-deploy}" in
deploy)
deploy
;;
rollback)
rollback
;;
backup)
backup
;;
*)
echo "用法: $0 {deploy|rollback|backup}"
exit 1
;;
esac
附录:常用命令速查表
表格
| 命令 | 作用 | 示例 |
|---|---|---|
echo |
输出文本 | echo "hello" |
read |
读取输入 | read -p "提示: " var |
test / [ ] |
条件测试 | [ $a -eq $b ] |
expr |
算术运算 | expr 1 + 2 |
let |
算术运算 | let a=1+2 |
$(( )) |
算术运算 | echo $((1+2)) |
source / . |
执行脚本(当前环境) | source script.sh |
chmod |
修改权限 | chmod +x script.sh |
cut |
按列切割 | cut -d: -f1 /etc/passwd |
sort |
排序 | sort -n file.txt |
uniq |
去重 | sort file | uniq -c |
wc |
统计行数/字数 | wc -l file.txt |
find |
查找文件 | find / -name "*.sh" |
xargs |
参数传递 | find . -name "*.log" | xargs rm |
curl |
网络请求 | curl -o file.zip URL |
wget |
下载文件 | wget URL |
写在最后
Shell 脚本学习心法:
-
先抄后改:把本文档的代码全部敲一遍,不要复制粘贴
-
小步快跑:每个知识点写一个小脚本验证,不要一次性写大脚本
-
善用
set -e:在脚本开头加set -e,遇到错误立即退出,避免隐患 -
多用双引号:变量尽量用
"$var"包裹,防止空格和特殊字符导致的问题 -
多查
man手册:man bash是最权威的参考书
关键口诀:变量加$,判断加空格,循环有结构,函数要复用,文本三剑客,实战出真知。
浙公网安备 33010602011771号