首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
【FDE系列】阶段2:Day 38:Shell 脚本 — 把命令串起来自动跑
📅 2026/10/24 5:23:53
✍️ 爱科研究院
👁 阅读 3,247
前言FDE系列内容总纲【大纲】FDE 前沿部署工程师学习系列教程-CSDN博客前置课程列表见文档结尾附录。阶段2·Day 38Shell 脚本 — 把命令串起来自动跑FDE 学习系列教程 · 第二阶段 · 第 4 周 · Day 3预计时长3 小时 | 难度★★★☆☆ | 前置知识Day 36-37 文件操作、权限、grep一句话目标掌握 Shell 脚本的变量、参数、条件、循环、函数和退出码写出一个带参数校验、自动打包、日期命名的备份脚本。 开场从手敲命令到写程序指挥命令前两天你已经能在终端里呼风唤雨了。但发现没有——有些操作你在反复敲每天备份tar -czf ...、ls -lh、记日志五条命令敲一遍每次部署拉代码、停服务、装依赖、启服务、健康检查八条命令每周巡检查磁盘、查进程、查端口……人不怕一次干得多就怕同样的事干一百遍。漏敲一条、顺序错一步就要排障半天。Shell 脚本就是解决这个的把一串命令写进一个文件配上变量、判断、循环以后敲一次脚本名全套动作自动跑完。没有脚本 有脚本 ───────── ────── $ tar -czf ... $ ./backup.sh ~/fde_project $ ls -lh ... ✅ 备份完成 $ echo 时间 log.log 文件fde_project_backup_20260918.tar.gz $ 检查目录在不在… 大小2.1M 10 个动作全靠脑子记 一条命令脚本自己全干了今天难度比前两天高一些语法细节多别想着一次全背下来——先跟着敲通理解脚本能干嘛细节以后随用随查。 一、Shell 脚本长什么样第一个脚本hello.sh在~/fde_lab里新建文件可以用nano hello.sh或在 VS Code 里编辑#!/bin/bash # # 我的第一个 Shell 脚本 # # 开头的行都是注释不会执行 # echo Hello, FDE! echo 当前用户$(whoami) echo 当前目录$(pwd) echo 今天是$(date %Y-%m-%d %H:%M)逐行解释代码含义#!/bin/bashshebang告诉系统用 /bin/bash 来解释执行这个脚本。脚本第一行固定这么写# 注释井号开头是注释给人看的机器忽略echo ...往屏幕打印一行文字相当于 Python 的 print$(命令)命令替换先执行括号里的命令把输出结果塞回字符串里运行脚本的两种方式# 方式一加执行权限后直接运行推荐正式姿势 chmod x hello.sh ./hello.sh # Hello, FDE! # 当前用户fde # 当前目录/home/fde/fde_lab # 今天是2026-09-18 14:30 # 方式二用 bash 解释器直接跑不需要 x 权限临时测试方便 bash hello.sh⚠️为什么是./hello.sh而不是hello.sh因为 Linux 默认不会在当前目录里找命令只在 PATH 环境变量指定的系统目录找。加./等于明确告诉系统我要运行当前目录下这个文件。date 命令备份脚本的日期生成器今天的实战脚本要用先单独练熟date %Y-%m-%d # 2026-09-18 date %Y%m%d_%H%M%S # 20260918_143052备份文件命名用这个 date %Y-%m-%d %H:%M:%S # 2026-09-18 14:30:52日志时间戳用这个格式符%Y年%m月%d日%H时%M分%S秒。 二、变量Shell 里存东西Shell 变量语法和 Python 很不一样等号两边绝对不能有空格这是新手第一大坑# 定义变量 NAME工单系统 # ⭐ 等号两边紧贴写 NAME x 会报错 VERSION1.0 # 使用变量加 $ echo $NAME v$VERSION # 工单系统 v1.0 echo ${NAME}_backup # 加花括号明确变量边界工单系统_backup # 命令替换把命令输出存进变量 TODAY$(date %Y%m%d) FILE_COUNT$(ls *.sh | wc -l) echo 今天是 $TODAY当前目录有 $FILE_COUNT 个脚本❌ 错误写法 ✅ 正确写法 NAME 工单系统 NAME工单系统 Shell 会以为 NAME 是个命令 echo 我的名字是 NAME echo 我的名字是 $NAME 没加 $打印出原文 NAME echo $NAME_backup echo ${NAME}_backup 系统找名为 NAME_backup 的变量 花括号划清边界引号的门道双引号 vs 单引号NAME工单系统 echo 你好 $NAME # 你好 工单系统 ← 双引号变量会被替换 echo 你好 $NAME # 你好 $NAME ← 单引号原样输出不替换 echo 今天 $(date %Y) # 双引号里命令替换也生效 经验法则字符串一律用双引号包起来尤其含变量、含空格、含路径时。单引号只在你明确想原样输出、啥都不解析时用。给变量设默认值BACKUP_DIR${2:-./backups} # 如果第2个参数没给就用 ./backups echo ${LEVEL:-普通} # LEVEL 没定义时显示普通 # ${变量:-默认值} 这个写法今天备份脚本就会用 三、位置参数让脚本接收外部输入脚本想复用就得能接收参数就像 Python 脚本里的sys.argv。新建args.sh#!/bin/bash echo 脚本自己叫$0 echo 第 1 个参数$1 echo 第 2 个参数$2 echo 第 3 个参数$3 echo 参数总个数$# echo 所有参数$chmod x args.sh ./args.sh /home/fde /backup 强制模式 # 脚本自己叫./args.sh # 第 1 个参数/home/fde # 第 2 个参数/backup # 第 3 个参数强制模式 # 参数总个数3 # 所有参数/home/fde /backup 强制模式特殊参数速查符号含义$0脚本自己的名字$1 $2 ...第 1、2...个参数$#参数个数$所有参数的列表循环时用$?上一条命令的退出码下面细讲$$当前脚本的进程号 四、退出码命令在说自己成功没有每条命令执行完都会留一个数字存在$?里表示执行结果ls /home # 成功 echo $? # 0 ← 0 表示成功 ls /一个不存在的目录 echo $? # 2 ← 非 0 表示失败不同数字代表不同错误 grep 不存在的字 hello.sh echo $? # 1 ← grep 没匹配到也算非0┌──────────────────────────────────────┐ │ 退出码约定 │ │ 0 成功 │ │ 非 0 失败1/2/126/127... │ │ │ │ 脚本用法 │ │ exit 0 正常结束 │ │ exit 1 异常退出参数错等 │ └──────────────────────────────────────┘这给了脚本判断成败的能力——比如健康检查脚本就是根据curl的退出码判断服务死活。明天会大量用到。 五、条件判断if基本结构if [ 条件 ]; then 条件成立时做的事 elif [ 另一个条件 ]; then 另一个条件成立时 else 都不成立时 fi头号语法坑[后面和]前面必须有空格if [$1 ]会直接报语法错误。因为[在 Shell 里其实是个命令test 的简写它需要空格把自己和参数分开。字符串判断if [ $1 ]; then echo 你没传参数 exit 1 fi if [ $1 start ]; then echo 准备启动 fi if [ $1 ! start ]; then echo 参数不是 start fi # 判空更专业的写法 if [ -z $1 ]; then # -z zero length字符串长度为0 echo 参数为空 fi if [ -n $1 ]; then # -n 非空 echo 收到参数$1 fi文件/目录判断备份脚本的核心校验if [ -d $DIR ]; then # -d directory存在且是目录 echo $DIR 是个目录 fi if [ -f $FILE ]; then # -f file存在且是普通文件 echo $FILE 是个文件 fi if [ ! -e $PATH ]; then # -e exists存在! 表示非 echo $PATH 不存在 fi if [ -r $FILE ]; then # -r 可读 / -w 可写 / -x 可执行 echo 文件可读 fi测试符含义-d是目录-f是普通文件-e存在不管类型-r -w -x可读/可写/可执行-z字符串为空-n字符串非空!字符串相等/不等数字比较注意不是 if [ $COUNT -gt 5 ]; then ... # -gt greater than 大于 if [ $COUNT -ge 5 ]; then ... # -ge 大于等于 if [ $COUNT -lt 5 ]; then ... # -lt 小于 if [ $COUNT -le 5 ]; then ... # -le 小于等于 if [ $COUNT -eq 5 ]; then ... # -eq 等于 if [ $COUNT -ne 5 ]; then ... # -ne 不等于⚠️ 数字比较用-eq -lt -gt这套不能直接写那俩在 Shell 里是重定向符号。字符串相等才用。用 和 || 写简单条件[ -d /home/fde ] echo 目录在 # 前面成功才执行后面 [ -f a.txt ] || echo 文件不存在 # 前面失败才执行后面 mkdir -p /tmp/test cd /tmp/test # 建目录成功后才进去链式保证 六、循环for 和 whilefor遍历一个清单# 遍历文件 for file in *.sh; do echo 找到脚本$file done # 遍历给定列表 for name in 李工 王工 赵工; do echo 通知$name 你好 done # 遍历数字序列 for i in 1 2 3 4 5; do echo 第 $i 次巡检 done # 更灵活的数字序列用 {起始..结束} for i in {1..5}; do echo 编号 $i done # 遍历脚本收到的所有参数 for arg in $; do echo 处理参数$arg donewhile条件为真就一直转COUNT0 while [ $COUNT -lt 5 ]; do COUNT$((COUNT 1)) # $(( )) 是整数运算 echo 计数$COUNT done整数运算语法echo $((3 2)) # 5 NUM10 NUM$((NUM 1)) # 自增 echo $((NUM * 2)) # 22实战小例子批量改名# 给当前目录所有 .txt 文件复制一份 .bak for f in *.txt; do cp $f ${f}.bak echo 已备份 $f → ${f}.bak done 七、函数把重复动作打包函数让脚本有结构、能复用和 Python 的函数思想一样# 定义函数function 关键字可省略 log_info() { echo [$(date %H:%M:%S)] [INFO] $1 } log_error() { echo [$(date %H:%M:%S)] [ERROR] $1 2 # 2 输出到错误流 } # 调用函数内部也用 $1 $2 接参数 log_info 脚本开始 log_info 正在处理数据 log_error 设备无响应 # [14:30:52] [INFO] 脚本开始 # [14:30:52] [INFO] 正在处理数据 # [14:30:52] [ERROR] 设备无响应函数要点定义 函数名() { 命令... } 调用 函数名 参数1 参数2 不加括号 取参 函数内部用 $1 $2$ 是全部参数 局部变量local x1不加 local 就是全局变量 返回值return 数字退出码想返回字符串用 echo $()# 函数返回字符串的标准套路 get_size() { du -h $1 | cut -f1 } SIZE$(get_size hello.sh) # 用 $() 接住函数的 echo 输出 echo hello.sh 大小$SIZE️ 八、实战自动备份脚本本课重头戏把今天学的全部用上。新建backup.sh#!/bin/bash # # 自动备份脚本 v1.0 # 功能把指定目录打包压缩按日期命名输出报告 # 用法./backup.sh 要备份的目录 [备份存放目录] # 例子./backup.sh ~/fde_project ./backups # set -euo pipefail # ↑ 严格模式出错即停、未定义变量报错、管道失败即失败 # Day 40 会细讲今天先照抄它能让脚本更可靠 # ---------- 1. 接收并校验参数 ---------- SOURCE_DIR${1:-} # 源目录必填 BACKUP_DIR${2:-./backups} # 备份目录选填默认 ./backups if [ -z $SOURCE_DIR ]; then echo ❌ 错误没有指定要备份的目录 echo 用法$0 源目录 [备份目录] echo 例子$0 ~/fde_project ./backups exit 1 fi if [ ! -d $SOURCE_DIR ]; then echo ❌ 错误源目录不存在 → $SOURCE_DIR exit 1 fi # ---------- 2. 准备备份目录 ---------- mkdir -p $BACKUP_DIR # ---------- 3. 生成备份文件名目录名 时间戳---------- TIMESTAMP$(date %Y%m%d_%H%M%S) DIR_NAME$(basename $SOURCE_DIR) # 从完整路径里抠出最后一段目录名 ARCHIVE${BACKUP_DIR}/${DIR_NAME}_backup_${TIMESTAMP}.tar.gz # ---------- 4. 打包压缩 ---------- echo 正在备份$SOURCE_DIR echo 目标文件$ARCHIVE # tar 参数c 创建 z gzip压缩 f 指定文件 # -C 先切到源目录的父目录保证压缩包里不带一长串绝对路径 tar -czf $ARCHIVE -C $(dirname $SOURCE_DIR) $DIR_NAME # ---------- 5. 输出结果报告 ---------- SIZE$(du -h $ARCHIVE | cut -f1) echo ✅ 备份完成 echo 文件$ARCHIVE echo 大小$SIZE echo 时间$(date %Y-%m-%d %H:%M:%S)跑起来chmod x backup.sh # 正常备份 ./backup.sh ~/fde_project # 正在备份/home/fde/fde_project # 目标文件./backups/fde_project_backup_20260918_143052.tar.gz # ✅ 备份完成 # 文件./backups/fde_project_backup_20260918_143052.tar.gz # 大小12K # 时间2026-09-18 14:30:52 # 不传参数 → 校验拦截 ./backup.sh # ❌ 错误没有指定要备份的目录 # 用法./backup.sh 源目录 [备份目录] # 源目录不存在 → 校验拦截 ./backup.sh /not/exist # ❌ 错误源目录不存在 → /not/exist # 验证压缩包内容 tar -tzf ./backups/fde_project_backup_*.tar.gz | head脚本逻辑流程图关键命令拆解tartar -czf 备份包.tar.gz 要打包的东西 │││ ││└─ f file指定生成的文件名 │└── z gzip压缩不加 z 只打包不压缩文件大 └─── c create创建新压缩包 tar -tzf 备份包.tar.gz # t list列出包里有啥不释放 tar -xzf 备份包.tar.gz # x extract解包释放 为什么要-C $(dirname ...)如果直接tar -czf x.tar.gz /home/fde/fde_project解压时会还原出完整的home/fde/fde_project/...层级用-C切到父目录再打包解压出来就是干净的fde_project/一个文件夹。这是备份脚本的专业细节。 本课小结知识点语法一句话脚本头#!/bin/bash指定 bash 解释器输出echo打印文字命令替换$(命令)拿命令输出变量NAMEx取值$NAME等号两边无空格默认值${1:-默认}参数没给就用默认参数$0 $1 $# $脚本名/第N参/个数/全部退出码$?exit 10 成功非0失败条件if [ ... ]; then括号两边有空格文件判断-d -f -e -z目录/文件/存在/空串数字比较-eq -lt -gt不能用 循环for x in 列表; do/while [ ]; do遍历 / 条件循环运算$((a1))整数运算函数f() {...}调用f 参数内部用 $1打包tar -czf/-tzf/-xzf建/看/解 核心认知Shell 脚本 命令 编程逻辑。前两周学的命令是砖块今天学的变量/条件/循环/函数是水泥和图纸。写脚本的思路永远是先手动把命令跑通 → 再把固定值换成变量/参数 → 加上校验和日志 → 存成.sh复用。 课后练习练习 1参数与判断约 20 分钟写check_path.sh接收一个路径参数没传参数 → 提示用法并exit 1传的是目录 → 输出这是目录里面有 N 个文件提示ls $1 \| wc -l传的是文件 → 输出这是文件大小为 XX提示du -h都不是 → 输出路径不存在练习 2循环批量处理约 20 分钟写batch_backup.sh接收多个文件参数用$把每个文件复制成文件名.bak并用 for 循环逐个打印已备份 xxx。测试./batch_backup.sh a.txt b.txt hello.sh。练习 3升级备份脚本约 40 分钟三选二实现在backup.sh基础上加功能日志功能所有输出同时显示在屏幕并追加写入backup.log提示把报告部分用tee -a backup.log或定义 log 函数保留份数脚本开头加第三个参数 K默认 7备份完成后只保留最新的 K 份更老的自动删提示ls -t 目录/*.tar.gz | tail -n $((K1)) | xargs -r rm备份后校验用tar -tzf $ARCHIVE /dev/null验证压缩包没损坏失败就报错退出用$?判断 下节预告备份脚本能跑了。但有个问题你的 FastAPI 服务一关终端就停总不能一直开着窗口守着它。明天综合实战解决服务常驻问题nohup关掉终端服务照样跑screen给服务开一个关不掉的房间随时能回去看tail -f实时盯日志crontab让备份脚本每天凌晨 2 点自动执行不用人管健康检查脚本服务挂了自动拉起来学完明天你的 API 就有了一台无人值守服务器的雏形。明天见附录前置课程列表阶段一认知启蒙AI 认知与 FDE 角色AI 认知【FDE系列】阶段1Day 1AI 层级关系 — 四个嵌套的圈-CSDN博客【FDE系列】阶段1Day 2AI 三阶段发展史 — 会认 → 会判断 → 会创造-CSDN博客【FDE系列】阶段1Day 3符号 AI vs 机器学习 — 两条路线的本质区别-CSDN博客【FDE系列】阶段1Day 4Transformer 的历史意义 — 2017 年的分水岭-CSDN博客【FDE系列】阶段1Day 5本周复习与自测 — 检验你的 AI 认知地基-CSDN博客【FDE系列】阶段1Day 6Transformer 架构 — 一张图纸盖出千千万万栋楼-CSDN博客【FDE系列】阶段1Day 7LLM 本质 — 文字接龙机器-CSDN博客【FDE系列】阶段1Day 8Token — 模型眼中的最小单位-CSDN博客【FDE系列】阶段1Day 9AI 幻觉 — 为什么会一本正经地胡说八道-CSDN博客【FDE系列】阶段1Day 10上下文窗口 — 模型的记忆力上限 本周复习-CSDN博客【FDE系列】阶段1Day 11Prompt — 给模型立规矩-CSDN博客【FDE系列】阶段1Day 12Memory — 让模型记住上下文【FDE系列】阶段1Day 13RAG — 给模型配图书管理员-CSDN博客【FDE系列】阶段1Day 14Tool Use — 让模型动手操作-CSDN博客【FDE系列】阶段1Day 15MCP — 统一的工具接口标准 第三周复习-CSDN博客FDE 基础概念【FDE系列】阶段1Day 16什么是 FDE — 把 AI 变成客户结果的人-CSDN博客【FDE系列】阶段1Day 17FDE vs 传统实施 — 三大本质区别-CSDN博客【FDE系列】阶段1Day 18FDE 三重身份 C6 胜任力模型-CSDN博客【FDE系列】阶段1Day 19七阶段行动路径 行业经验的价值-CSDN博客【FDE系列】阶段1Day 20阶段总结与产出物 — 第一阶段收官-CSDN博客阶段二技术地基Python FastAPI SQL Docker API 集成Python基础【FDE系列】阶段2Day 21Python 环境搭建 — 写出你的第一行代码-CSDN博客【FDE系列】阶段2Day 22变量、数据类型、条件判断 — Python 的“记忆“和“判断“-CSDN博客【FDE系列】阶段2Day 23循环与函数 — 让代码跑 100 遍、把逻辑打包复用-CSDN博客【FDE系列】阶段2Day 24数据结构 — 列表、字典、集合、元组-CSDN博客【FDE系列】阶段2Day 25文件读写与 JSON — 让程序连通外部数据第一周收官-CSDN博客【FDE系列】阶段2Day 26模块化编程 — 把代码拆成“抽屉柜“-CSDN博客【FDE系列】阶段2Day 27异常处理与日志 — 让程序“摔不烂、查得到“-CSDN博客FastAPI入门到进阶【FDE系列】阶段2Day 28FastAPI 入门 — 把你的函数变成 API 服务-CSDN博客【FDE系列】阶段2Day 29FastAPI 进阶 — Pydantic 模型与完整 CRUD 实战-CSDN博客【FDE系列】阶段2Day 30生产代码规范 — 测试、类型注解、配置管理第二周收官-CSDN博客SQL基础【FDE系列】阶段2Day 31SQL 基础 — 增删改查一把梭-CSDN博客【FDE系列】阶段2Day 32多表查询 — JOIN 与聚合-CSDN博客【FDE系列】阶段2Day 33进阶查询 — 窗口函数与 CTE-CSDN博客【FDE系列】阶段2Day 34数据清洗 — 把脏数据捋干净-CSDN博客【FDE系列】阶段2Day 35Python SQL — 工单接入 MySQL 本周收官-CSDN博客【FDE系列】阶段2Day 36Linux 入门与文件操作 — 扔掉鼠标的第一天-CSDN博客【FDE系列】阶段2Day 37权限、进程与文本三剑客-CSDN博客
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/24 5:23:53
Kafka请求分发器:completedReceives里的请求是怎么被处理的
2026/10/24 5:23:53
Java后端做前后端设计,数据库表到底该拆多细?
2026/10/24 5:18:53
trackerslist 实战:76 个公共 tracker 装进 qBittorrent,5 分钟跑通
2026/10/24 6:23:56
freehire Airswift 适配器设计解析:用“过期标记”把已关闭职位从“读不懂”中区分出来
2026/10/24 6:23:56
《普林斯顿微积分读本》PDF高效阅读指南:从零基础到应试全攻略
2026/10/24 6:23:56
土木本科生的 STM32 探索之旅:以物联网监测为起点(第一篇)
2026/10/24 6:23:56
简单EKF滤波算法的MATLAB软件仿真
2026/10/24 6:23:56
AXI总线详细完整介绍:AMBA AXI协议与FPGA实现
2026/10/24 6:18:55
MFC按钮不显示?深入解析CButton::Create与样式组合
2026/10/24 0:02:52
UDP协议实现局域网音量与关机控制:后台常驻与批量管理
2026/10/24 0:02:52
MATLAB缩进快捷键与批量格式化实战指南
2026/10/24 0:02:52
用Poco实现Qt风格信号与槽的跨平台C++方案
2026/10/24 4:01:50
OpenResearch实战:Claude Code、Codex、OpenCode、Cursor 工作流搭建与故障排查
2026/10/24 4:01:49
知识管理核弹:用Obsidian构建第二大脑的工程化方案(TaoToken 统一 Key 接入版)
2026/10/24 0:59:24
金融AI落地指南:从调研报告到技术选型与工程实践
2026/10/23 10:58:39
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/23 10:58:39
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/23 10:58:39
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)