欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > Shell 脚本开发从入门到实战

Shell 脚本开发从入门到实战

2025/4/18 20:57:04 来源:https://blog.csdn.net/print_helloword/article/details/147127335  浏览:    关键词:Shell 脚本开发从入门到实战

第1章:什么是 Shell 与 Shell 脚本?

一、Shell 是什么?

  • Shell 是一个命令解释器,是你在 Linux 里敲命令的地方。
  • 你平时用的命令如 cdlsecho,其实都由 Shell 来解析执行。
  • 最常见的 Shell 是 Bash,绝大多数 Linux 系统默认都用它。

二、什么是 Shell 脚本?

  • Shell 脚本就是一系列 Shell 命令的集合,保存成一个 .sh 文件。
  • 本质上:你平时一条一条敲的命令,写成脚本就能一次性自动运行。
  • 示例场景:
    • 每天定时备份数据库
    • 启动一堆服务
    • 自动上传文件、发通知等

三、第一个 Shell 脚本实战

步骤1:创建一个脚本文件
vim hello.sh
步骤2:写入下面内容
#!/bin/bash
# 这是我的第一个Shell脚本echo "Hello, Shell!"

第一行 #!/bin/bash 是固定写法,告诉系统用 Bash 来执行脚本。

步骤3:保存退出并赋予执行权限
chmod +x hello.sh
步骤4:运行脚本
./hello.sh

输出应该是:

Hello, Shell!

四、脚本中的几个细节

  • 所有行首以 # 开头的是注释,系统会忽略。
  • 每一行相当于你在终端执行的一条命令。
  • 脚本默认按顺序逐行执行。

五、本节练习

自己创建一个脚本 myinfo.sh,内容如下:

#!/bin/bashecho "欢迎来到 Shell 脚本世界!"
echo "当前用户:$USER"
echo "当前时间:$(date)"
echo "当前目录:$(pwd)"

运行后会打印你的用户名、当前时间和所在目录。


第2章:变量与运算符

一、变量的定义与使用

1. 定义变量(= 两边不能有空格)
name="张三"
age=25
2. 使用变量:$变量名${变量名}
echo "姓名:$name"
echo "年龄:${age}"

二、变量赋值的注意事项

a=10          # 正确
a = 10        # 错误,不能有空格

三、特殊变量

变量说明
$0当前脚本名称
$1~$9传入脚本的第1~9个参数
$@传入的所有参数(可用于循环)
$#参数个数
$$当前脚本的进程ID
$?上一条命令的返回值(0成功,非0失败)
示例:
#!/bin/bash
echo "脚本名:$0"
echo "第一个参数:$1"
echo "参数个数:$#"

运行:

./myscript.sh hello

输出:

脚本名:./myscript.sh
第一个参数:hello
参数个数:1

四、字符串操作

拼接字符串:
first="张"
last="三"
full=$first$last
echo "姓名:$full"
获取字符串长度:
str="abc123"
echo "长度:${#str}"  # 输出 6

五、数值运算(整数)

Bash 默认只支持整数,用三种方式:

1. 使用 let
let a=5+3
echo $a  # 输出 8
2. 使用 expr
a=`expr 5 + 3`
echo $a

注意:运算符前后必须有空格。

3. 使用 $((...))(推荐)
a=$((5 + 3))
echo $a

六、本节小练习

请写一个脚本:userinfo.sh,功能如下:

#!/bin/bashname="李四"
age=30
year=$((2025 - age))echo "姓名:$name"
echo "年龄:$age"
echo "出生年份:$year"

运行后输出:

姓名:李四
年龄:30
出生年份:1995

第3章:流程控制语句(if / case / for / while)

一、if 判断语句

基本语法:
if [ 条件 ]
then命令1命令2
fi
示例:
age=20if [ $age -ge 18 ]; thenecho "成年人"
elseecho "未成年人"
fi
常用条件判断符号:
条件含义
-eq等于
-ne不等于
-gt大于
-lt小于
-ge大于等于
-le小于等于
== / !=字符串比较

二、文件判断(常用于运维脚本)

if [ -f "a.txt" ]; thenecho "a.txt 是一个文件"
fiif [ -d "/home" ]; thenecho "/home 是一个目录"
fi
表达式含义
-f FILE是否是普通文件
-d DIR是否是目录
-e FILE是否存在
-r/-w/-x FILE是否可读 / 可写 / 可执行

三、case 多分支选择(类似 switch)

read -p "请输入操作命令(start|stop|restart):" cmdcase $cmd instart)echo "正在启动服务...";;stop)echo "正在停止服务...";;restart)echo "正在重启服务...";;*)echo "未知命令";;
esac

四、for 循环

遍历列表:
for name in 张三 李四 王五
doecho "你好,$name"
done
遍历数字:
for i in {1..5}
doecho "第 $i 次"
done

五、while 循环

i=1
while [ $i -le 5 ]
doecho "当前是第 $i 次"i=$((i + 1))
done

六、本节练习题

写一个脚本 score.sh

#!/bin/bashread -p "请输入成绩:" scoreif [ $score -ge 90 ]; thenecho "优秀"
elif [ $score -ge 70 ]; thenecho "良好"
elif [ $score -ge 60 ]; thenecho "及格"
elseecho "不及格"
fi

第4章:函数与脚本模块化

在脚本越来越复杂时,把逻辑封装成函数可以让脚本更清晰、复用性更强。


一、函数的定义与调用

语法1:带 function 关键字
function say_hello() {echo "Hello!"
}
语法2:省略 function
say_hello() {echo "Hello!"
}
调用函数:
say_hello

二、函数传参

函数可以像脚本一样,通过 $1$2 来接收参数:

greet() {echo "你好,$1!今天是 $2。"
}greet "张三" "星期四"

输出:

你好,张三!今天是 星期四。

三、函数返回值

  • return 只能返回 整数(状态码),不适合传数据
  • 可以通过 echo 输出 + 命令替换的方式返回字符串
示例1:返回状态码
check_even() {if [ $(( $1 % 2 )) -eq 0 ]; thenreturn 0elsereturn 1fi
}check_even 4
if [ $? -eq 0 ]; thenecho "偶数"
elseecho "奇数"
fi
示例2:返回字符串
get_name() {echo "张三"
}name=$(get_name)
echo "姓名是:$name"

四、脚本模块化(多个脚本协作)

你可以将常用函数写在一个文件里,用其他脚本通过 source 引入使用。

step1:创建函数库 utils.sh
say_hi() {echo "你好,我是工具函数"
}
step2:主脚本中引入使用
#!/bin/bash
source ./utils.shsay_hi

五、本章练习

写一个函数 calc_sum,输入两个参数,输出它们的和:
calc_sum() {sum=$(( $1 + $2 ))echo "$1 + $2 = $sum"
}calc_sum 4 6

第5章:文件判断、重定向与文本处理


一、文件存在性与属性判断

1. 判断文件是否存在
if [ -e "file.txt" ]; thenecho "文件存在"
fi
2. 常用文件判断符号
表达式含义
-e file是否存在
-f file是否是普通文件
-d dir是否是目录
-s file文件非空
-r file是否可读
-w file是否可写
-x file是否可执行
示例:判断一个目录是否存在,不存在则创建
dir="/data/backup"
if [ ! -d "$dir" ]; thenmkdir -p "$dir"echo "目录已创建:$dir"
elseecho "目录已存在:$dir"
fi

二、输入输出重定向

1. 重定向输出(覆盖 / 追加)
echo "一行内容" > file.txt     # 覆盖写入
echo "再来一行" >> file.txt    # 追加写入
2. 重定向错误输出
command 2> error.log   # 仅错误输出到 error.log
command > out.log 2>&1 # 正常和错误都写入 out.log

三、文本内容处理:核心工具

1. cat:查看文件内容
cat file.txt
2. head / tail:查看前几行/后几行
head -n 5 file.txt
tail -n 10 file.txt
3. cut:按列提取文本内容
cat users.csv | cut -d ',' -f 2   # 提取第2列(用,分隔)
4. grep:关键词搜索
grep "ERROR" /var/log/syslog
5. awk:按列处理文本,超强工具
awk -F ',' '{print $1, $3}' users.csv
6. sed:流编辑器,可做替换、删除行等
sed 's/错误/成功/g' log.txt     # 全文替换
sed -n '5,10p' file.txt         # 显示第5到10行

四、本章综合示例

日志扫描:提取最近系统错误日志
#!/bin/bash
log="/var/log/syslog"grep "ERROR" "$log" | tail -n 20 > recent_errors.log
echo "最近错误已提取到 recent_errors.log"
CSV 提取:获取手机号列表(第3列)
#!/bin/bash
cut -d ',' -f 3 users.csv > phones.txt
echo "手机号已提取完毕"

五、本章练习题

  1. 判断 /etc/passwd 文件是否存在,如果存在则统计其行数。
  2. 提取 backup.log 文件中最近的包含关键字 fail 的前 10 条记录。
  3. data.txt 中的所有 “null” 替换为 “空值”。

第6章:Shell 工具组合实战(find、xargs、tar、scp、curl 等)


一、find:查找文件

基本用法
find 路径 [条件] [操作]
常见示例
# 查找所有 .log 文件
find /var/log -name "*.log"# 查找3天前修改的文件
find /data -mtime +3# 查找大于100M的文件
find / -type f -size +100M# 查找并删除7天前的备份文件
find /backup -name "*.tar.gz" -mtime +7 -exec rm -f {} \;

二、xargs:批量传参执行命令

cat files.txt | xargs rm -f

或结合 find 使用:

find . -name "*.tmp" | xargs rm -f

xargs 是 Shell 脚本中实现批处理的“神器”。


三、tar:打包压缩备份

# 打包当前目录
tar -czvf backup.tar.gz ./# 解压
tar -xzvf backup.tar.gz -C /tmp

四、scp:远程复制文件

# 上传文件
scp localfile.txt user@remote:/data/# 下载文件
scp user@remote:/data/backup.tar.gz ./

配合 SSH 免密登录使用更方便。


五、rsync:增量同步工具(强烈推荐用于部署)

rsync -avz ./dist/ user@remote:/data/web/
  • -a:归档模式(保留时间、权限等)
  • -v:显示详细过程
  • -z:压缩传输

六、curl:调用接口(之前已经讲过)

常用于脚本中调用 webhook、发送通知、上报状态等。

curl -X POST http://api.example.com/report \-H "Content-Type: application/json" \-d '{"status":"ok","time":"2025-04-10 12:00:00"}'

七、本章综合实战:自动打包 + 清理 + 上报

#!/bin/bash# 定义变量
BAK_DIR="/data/backup"
NOW=$(date +"%Y%m%d%H%M%S")
FILENAME="backup_$NOW.tar.gz"# 打包
tar -czf "$BAK_DIR/$FILENAME" /data/mingdao# 删除7天前的旧备份
find $BAK_DIR -name "*.tar.gz" -mtime +7 -exec rm -f {} \;# 上报结果
curl -X POST http://api.example.com/backup/report \-H "Content-Type: application/json" \-d "{\"filename\":\"$FILENAME\", \"time\":\"$NOW\"}"

八、本章练习题

  1. 使用 find 查找 /tmp 目录下所有 .log 文件并删除。
  2. 编写一个脚本:打包 /etc 目录,保存到 /data/backup,并保留最近3份。
  3. 编写一个自动化部署脚本,使用 rsync 将本地 /dist 同步到远程服务器指定目录。

第7章:Shell 实战项目篇


实战1:数据库定时备份 + 接口上报

场景说明:

你想每天定时备份 MySQL 和 MongoDB,并在完成后通过 curl 报告备份信息。

脚本:backup_and_report.sh
#!/bin/bash# 1. 定义开始时间
start_time=$(date +"%Y-%m-%d %H:%M:%S")# 2. 执行备份命令(这里用 docker 容器)
container_id=$(docker ps | grep community | awk '{print $1}')
docker exec -it "$container_id" bash -c 'source /entrypoint.sh && backup mysql mongodb file'# 3. 定义结束时间
end_time=$(date +"%Y-%m-%d %H:%M:%S")# 4. 找到最新备份路径
backup_dir="/data/mingdao/script/volume/data/backup"
latest=$(ls -td ${backup_dir}/*/ | head -n 1)# 5. 上报接口
curl -X POST http://api.example.com/backup/report \-H "Content-Type: application/json" \-d '{"startTime": "'"$start_time"'","endTime": "'"$end_time"'","backupPath": "'"$latest"'"}'
配合 crontab 每天凌晨1点执行:
0 1 * * * /bin/bash /opt/scripts/backup_and_report.sh >> /var/log/backup.log 2>&1

实战2:自动部署前端代码到远程服务器

脚本:deploy_frontend.sh
#!/bin/bashdist_dir="./dist"
remote_user="deploy"
remote_host="192.168.10.100"
remote_path="/data/www/"rsync -avz --delete "$dist_dir/" "$remote_user@$remote_host:$remote_path"
echo "部署完成:$(date)"

实战3:系统监控报警脚本

脚本:check_cpu.sh
#!/bin/bashthreshold=80
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print 100 - $8}')  # $8为空闲if [ $(echo "$cpu_usage > $threshold" | bc) -eq 1 ]; thencurl -X POST http://api.example.com/alert \-H "Content-Type: application/json" \-d '{"message": "CPU使用率过高:'"$cpu_usage"'%"}'
fi

实战4:交互式菜单工具脚本

脚本:menu.sh
#!/bin/bashwhile true; doecho "====== 系统工具菜单 ======"echo "1. 查看磁盘使用"echo "2. 查看内存使用"echo "3. 查看登录用户"echo "4. 退出"read -p "请输入选项[1-4]:" choicecase $choice in1) df -h ;;2) free -h ;;3) who ;;4) echo "Bye!"; exit 0 ;;*) echo "无效输入" ;;esac
done

实战5:每日自动压缩日志

#!/bin/bashlog_dir="/var/log/myapp"
backup_dir="/data/log_backup"
today=$(date +%F)mkdir -p "$backup_dir"
tar -czf "$backup_dir/logs_$today.tar.gz" "$log_dir"/*.log
rm -f "$log_dir"/*.log

本章练习题

  1. 写一个脚本定时将 /etc 目录打包上传到远程服务器。
  2. 写一个脚本每天查找 /tmp.log 文件并清理超过3天的文件。
  3. 写一个自动监测服务器磁盘占用超过 90% 就上报告警的脚本。

第8章 · 个人实战项目总结:自动化备份 + 压缩 + 清理 + 上报


脚本目标

每天凌晨 1 点自动执行以下操作:

  1. 判断磁盘空间是否足够
  2. 执行容器内数据库+文件备份命令
  3. 将备份目录压缩为 .tar.gz
  4. 删除原始目录
  5. 保留最近 3 个压缩文件
  6. 上报接口通知备份结果
  7. 生成详细执行日志
  8. 防止多次重复执行

脚本路径

/data/backup_and_notify.sh

最终正式版脚本

#!/bin/bash
set -euo pipefail   # 脚本遇错立即退出,禁止未定义变量,管道中出错也终止
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin# ========== 配置区域 ==========
LOG_FILE="/tmp/cron_exec.log"                         # 日志文件路径
LOCK_FILE="/tmp/backup.lock"                          # 锁文件路径(防止重复执行)
BACKUP_BASE="/backup_base" # 备份目录(需确保存在)
API_URL="http://your-api-host/your/endpoint"          # 上报接口地址(请替换)
DEBUG_MODE=false                                       # 调试开关,true 为跳过 docker 执行并模拟
# =================================log() {echo "[$(date +"%F %T")] $1" >> "$LOG_FILE"  # log 函数:统一日志格式写入
}# ========== 防重入锁机制 ==========
if [ -f "$LOCK_FILE" ]; thenold_pid=$(cat "$LOCK_FILE")if ps -p "$old_pid" > /dev/null 2>&1; thenlog "已有任务执行中(PID=$old_pid),跳过本次"exit 0elselog "锁文件存在但进程不存在,清理锁文件"rm -f "$LOCK_FILE"fi
fiecho $$ > "$LOCK_FILE"  # 写入当前脚本 PID 为锁文件# ========== 主流程开始 ==========
log "=== 备份任务开始 ==="
start_time=$(date +"%Y-%m-%d %H:%M:%S")# 1. 检查磁盘剩余空间是否大于当前已用空间(防爆盘)
disk_info=$(df -k "$BACKUP_BASE" | tail -1)
used_kb=$(echo "$disk_info" | awk '{print $3}')
avail_kb=$(echo "$disk_info" | awk '{print $4}')
log "磁盘空间 - 已用: ${used_kb}KB, 可用: ${avail_kb}KB"if [ "$avail_kb" -lt $((used_kb * 1)) ]; thenlog "剩余空间不足,终止备份"rm -f "$LOCK_FILE"exit 1
fi# 2. 查找名为 community 的 docker 容器
container_id=$(docker ps | grep community | awk '{print $1}')
if [ -z "$container_id" ]; thenlog "未找到包含 community 的容器,退出"rm -f "$LOCK_FILE"exit 1
fi
log "容器 ID: $container_id"# 3. 执行备份命令或生成模拟目录
if [ "$DEBUG_MODE" == "true" ]; thenlog "[调试模式] 跳过 docker 备份,生成模拟目录"mock_dir_name=$(date +"%Y%m%d%H%M%S")mock_dir_path="$BACKUP_BASE/$mock_dir_name"mkdir -p "$mock_dir_path"echo "模拟数据文件内容" > "$mock_dir_path/mock.txt"latest_backup="$mock_dir_path"
elselog "执行容器备份命令..."docker exec "$container_id" bash -c 'source /entrypoint.sh && backup mysql mongodb file'log "容器内备份完成"latest_backup=$(ls -td "$BACKUP_BASE"/*/ 2>/dev/null | head -n 1)
fiend_time=$(date +"%Y-%m-%d %H:%M:%S")# 4. 确认最新备份目录
if [ -z "$latest_backup" ]; thenlog "未找到最新备份目录,退出"rm -f "$LOCK_FILE"exit 1
filatest_dir_name=$(basename "$latest_backup")
log "最新备份目录:$latest_backup"# 5. 压缩目录为 .tar.gz
tar_file="$BACKUP_BASE/$latest_dir_name.tar.gz"
log "压缩目录为:$tar_file"
tar -zcvf "$tar_file" -C "$BACKUP_BASE" "$latest_dir_name" >> "$LOG_FILE" 2>&1if [ -f "$tar_file" ]; thenlog "压缩成功,删除原始目录"rm -rf "$latest_backup"
elselog "压缩失败:文件未生成"
fi# 6. 上报接口,上传 tar 包路径 + 起止时间
log "开始上报接口..."
post_result=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API_URL" \-H "Content-Type: application/json" \-d '{"startTime": "'"$start_time"'","endTime": "'"$end_time"'","backupPath": "'"$tar_file"'"}')if [ "$post_result" == "200" ]; thenlog "上报成功"
elselog "上报失败,状态码:$post_result"
fi# 7. 清理旧备份,仅保留最新 3 个 tar.gz 文件
log "开始清理旧备份(保留3份)"
old_tars=$(ls -1t "$BACKUP_BASE"/*.tar.gz 2>/dev/null | tail -n +4)
for f in $old_tars; dolog "删除旧文件:$f"rm -f "$f"
donelog "=== 备份任务结束 ==="# 8. 删除锁文件
rm -f "$LOCK_FILE"

定时任务 crontab 设置

crontab -e

添加:

0 1 * * * /bin/bash /data/backup_and_notify.sh >> /var/log/backup.log 2>&1

测试方式

  1. 设置 DEBUG_MODE=true 快速走完整流程
  2. 查看日志输出:
    • /var/log/backup.log
    • /tmp/cron_exec.log
  3. 检查是否压缩 .tar.gz 成功
  4. 检查是否自动清理旧文件
  5. 检查是否触发上报接口

第9章:Shell 脚本高阶技巧与调试优化


一、调试脚本的方法

1. 手动打印变量值(最常用)
echo "当前值是:$var"
2. 使用 set -x 开启调试模式
#!/bin/bash
set -x    # 开始调试
...你的代码...
set +x    # 关闭调试

会打印出每一行执行的命令和变量展开后的值,便于追踪问题。

3. 整体调试脚本执行
bash -x yourscript.sh

二、安全与健壮性写法

1. 脚本头部加入防御设置
#!/bin/bash
set -euo pipefail
  • -e: 一旦出错立即退出
  • -u: 使用未定义变量时立即退出
  • -o pipefail: 管道中任何一个命令出错就认为整个失败
2. 变量默认值
echo "用户名:${USERNAME:-guest}"

如果 USERNAME 未定义,则默认值为 guest

3. 防止空变量导致误删
# 错误写法(rm -rf $dir,如果 $dir 是空,会删当前目录)
rm -rf "$dir"# 安全写法
if [ -n "$dir" ]; thenrm -rf "$dir"
fi

三、优化脚本结构

1. 使用函数模块化脚本
backup_mysql() { ... }
backup_mongo() { ... }
send_report() { ... }main() {backup_mysqlbackup_mongosend_report
}main
2. 拆分配置文件

将常量配置提取到 config.sh,主脚本中用 source ./config.sh 引入,方便统一维护。


四、日志管理与错误输出

记录日志
log() {echo "[$(date +"%F %T")] $1" >> /var/log/myscript.log
}
捕获错误信息
some_command 2>> error.log

五、制作可执行命令脚本

chmod +x mytool.sh
mv mytool.sh /usr/local/bin/mytool

之后你就可以在任何地方输入 mytool 直接调用脚本了。


六、实践建议与面试亮点

  • 编写脚本时,错误处理永远优先
  • 函数 + 配置分离是结构清晰的关键;
  • 使用 set -euo pipefail 保证健壮性;
  • 利用 cron + curl + shell 可构建一整套自动化任务、监控、备份系统;
  • 如果要参加面试,可主动讲你写过哪些“脚本工具”、怎么处理日志、怎么保障安全 —— 是加分项!

七、本章练习与挑战

  1. 为你最常用的命令写一个简化脚本,如批量清理日志、上传打包文件。
  2. 尝试把项目中某个手工流程自动化为脚本,比如打包部署/数据库备份。
  3. 写一个脚本框架,支持日志输出、错误捕获、配置参数、分函数执行。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词