Skip to main content
今日目标:让脚本具备”逻辑判断”能力,并掌握数据库最核心的 CRUD(增删改查)操作。今天不只是写代码,而是要彻底理解 “如何让脚本根据条件执行不同操作”“如何高效地操作数据库数据” 以及 “为什么 UPDATE 和 DELETE 必须加 WHERE 条件”。这是编写自动化运维工具的第一步。

学习内容 (30 mins)

在开始写代码前,先搞懂这些核心概念,否则后面的代码你会看得云里雾里。
什么是流程控制?流程控制让脚本能够根据条件执行不同的操作,就像”如果…那么…否则”的逻辑。这是编写自动化脚本的核心能力。为什么需要流程控制?
  • 条件执行:根据文件是否存在、服务是否运行等条件执行不同操作
  • 批量处理:循环处理多个文件、多个服务器
  • 错误处理:检测错误并采取相应措施
  • 自动化决策:让脚本能够”思考”和”决策”
条件判断详解
  • 文件测试
    • [ -f "file.txt" ]:判断文件是否存在且为普通文件
    • [ -d "dir" ]:判断目录是否存在
    • [ -r "file.txt" ]:判断文件是否可读
    • [ -w "file.txt" ]:判断文件是否可写
    • [ -x "file.txt" ]:判断文件是否可执行
  • 字符串比较
    • [ "$name" == "admin" ]:判断字符串是否相等
    • [ "$name" != "admin" ]:判断字符串是否不相等
    • [ -z "$name" ]:判断字符串是否为空
    • [ -n "$name" ]:判断字符串是否非空
    • 注意:字符串比较必须用双引号包裹变量,防止空变量导致语法错误
  • 数值比较
    • [ $num -eq 10 ]:等于(equal)
    • [ $num -ne 10 ]:不等于(not equal)
    • [ $num -gt 10 ]:大于(greater than)
    • [ $num -lt 10 ]:小于(less than)
    • [ $num -ge 10 ]:大于等于(greater or equal)
    • [ $num -le 10 ]:小于等于(less or equal)
  • 逻辑运算符
    • [ ! -f "file.txt" ]:非(NOT),文件不存在
    • [ -f "file.txt" ] && [ -r "file.txt" ]:与(AND),文件存在且可读
    • [ -f "file.txt" ] || [ -d "dir" ]:或(OR),文件存在或目录存在
循环结构详解
  • for 循环
    • for i in {1..5}; do ... done:遍历数字序列
    • for file in *.log; do ... done:遍历文件列表
    • for user in alice bob charlie; do ... done:遍历字符串列表
    • 应用场景:批量处理文件、遍历数组、重复执行操作
  • while 循环
    • while [ condition ]; do ... done:条件为真时循环
    • 应用场景:监控服务状态、读取文件行、等待条件满足
  • until 循环
    • until [ condition ]; do ... done:条件为假时循环
    • 应用场景:等待服务启动、等待文件创建
什么是 CRUD?CRUD 是数据库操作的四个基本动作:
  • Create(创建):插入新数据
  • Read(读取):查询数据
  • Update(更新):修改数据
  • Delete(删除):删除数据
为什么 CRUD 是数据库的核心?
  • 所有数据库操作都基于这四个动作
  • 理解 CRUD 是学习复杂查询的基础
  • 掌握 CRUD 能处理 80% 的日常数据库操作
INSERT(插入)详解
  • 单条插入
    INSERT INTO users (username, email) VALUES ('alice', 'alice@example.com');
    
    • 指定字段:只插入需要的字段,其他字段使用默认值
    • 不指定字段:必须提供所有字段的值(不推荐)
  • 批量插入
    INSERT INTO users (username, email) VALUES 
    ('alice', 'alice@example.com'),
    ('bob', 'bob@example.com'),
    ('charlie', 'charlie@example.com');
    
    • 优势:一次插入多条数据,效率更高
    • 应用场景:初始化数据、批量导入
SELECT(查询)详解
  • 基础查询
    SELECT * FROM users;  -- 查询所有字段
    SELECT id, username FROM users;  -- 查询指定字段
    
  • WHERE 条件
    SELECT * FROM users WHERE status = 1;  -- 查询活跃用户
    SELECT * FROM users WHERE username = 'alice';  -- 精确匹配
    SELECT * FROM users WHERE created_at > '2024-01-01';  -- 日期比较
    
    • =:等于
    • !=<>:不等于
    • ><>=<=:比较运算符
    • LIKE:模糊匹配(后续课程会讲)
    • IN:在列表中
    • ANDOR:逻辑运算符
UPDATE(更新)详解
  • 基本语法
    UPDATE users SET email = 'new@example.com' WHERE username = 'alice';
    
  • 为什么必须加 WHERE?
    • 不加 WHERE:会更新所有记录(非常危险!)
    • 加 WHERE:只更新符合条件的记录
    • 最佳实践:更新前先用 SELECT 查询确认要更新的记录
  • 更新多个字段
    UPDATE users 
    SET email = 'new@example.com', status = 0 
    WHERE username = 'alice';
    
DELETE(删除)详解
  • 物理删除
    DELETE FROM users WHERE username = 'charlie';
    
    • 数据从数据库中永久删除,无法恢复
    • 适用于:测试数据、临时数据
  • 软删除(推荐)
    UPDATE users SET status = 0 WHERE username = 'charlie';
    
    • 不删除数据,只标记为”已删除”
    • 优势:可以恢复数据、保留历史记录
    • 适用于:生产环境、重要数据
  • 为什么必须加 WHERE?
    • 不加 WHERE:会删除所有记录(灾难性错误!)
    • 加 WHERE:只删除符合条件的记录
    • 最佳实践:删除前先用 SELECT 查询确认要删除的记录

代码任务 (90 mins)

1

Shell 逻辑脚本

编写 02_monitor.sh,模拟简单的服务监控:任务分解
  1. 定义日志目录变量
  2. 判断目录是否存在
  3. 如果不存在,创建目录
  4. 循环生成 5 个日志文件
  5. 每个日志文件包含服务器状态信息
#!/bin/bash
# 02_monitor.sh - 模拟服务监控脚本

# 定义日志目录
# 使用相对路径,脚本所在目录下的 logs 文件夹
LOG_DIR="./logs"

# 条件判断:检查目录是否存在
# [ -d "$LOG_DIR" ]:判断 $LOG_DIR 是否存在且为目录
# !:取反,即"如果目录不存在"
# 注意:变量必须用双引号包裹,防止路径中有空格导致错误
if [ ! -d "$LOG_DIR" ]; then
    # 如果目录不存在,创建目录
    # mkdir -p:递归创建目录,如果父目录不存在也会创建
    # -p 参数很重要:不会因为目录已存在而报错
    echo "Creating log directory..."
    mkdir -p "$LOG_DIR"
else
    # 如果目录已存在,提示信息
    echo "Log directory already exists."
fi

# for 循环:遍历数字序列
# {1..5}:生成序列 1, 2, 3, 4, 5
# 这是 Bash 的序列展开语法
for i in {1..5}; do
    # 构建文件名
    # $LOG_DIR/app_$i.log:例如 ./logs/app_1.log
    filename="$LOG_DIR/app_$i.log"
    
    # 写入日志内容
    # >:覆盖写入(如果文件已存在,会清空后写入)
    # 如果要用追加模式,使用 >>(后续课程会讲)
    echo "Server $i status: OK" > "$filename"
    
    # 输出创建信息
    echo "Created $filename"
done

# 循环结束后,输出总结信息
echo "All log files created successfully."
常见错误
  • if [ ! -d $LOG_DIR ] - 变量未加引号,路径有空格会报错
  • for i in 1 2 3 4 5 - 可以工作,但不如 {1..5} 简洁
  • mkdir $LOG_DIR - 如果目录已存在会报错,应该用 mkdir -p
  • if [ ! -d "$LOG_DIR" ] - 正确写法
  • for i in {1..5} - 正确写法
  • mkdir -p "$LOG_DIR" - 正确写法
验证步骤
  1. 保存文件后,添加执行权限:
    chmod +x 02_monitor.sh
    
  2. 运行脚本:
    ./02_monitor.sh
    
  3. 检查输出:
    • 应该看到 “Creating log directory…” 或 “Log directory already exists.”
    • 应该看到 5 个文件创建信息
    • 应该看到 “All log files created successfully.”
  4. 验证文件:
    ls -la logs/
    cat logs/app_1.log
    
  5. 测试边界情况:
    • 删除 logs 目录后再次运行脚本
    • 手动创建 logs 目录后运行脚本
2

SQL 数据操作

编写 02_crud.sql,对昨日的 users 表进行操作:任务分解
  1. 批量插入测试数据
  2. 查询活跃用户
  3. 更新用户邮箱
  4. 删除用户(演示物理删除,实际生产环境建议用软删除)
-- ============================================
-- Day 02: SQL CRUD 操作实战
-- ============================================

-- 确保使用正确的数据库
USE infra_db;

-- ============================================
-- 1. INSERT(插入数据)
-- ============================================

-- 批量插入用户数据
-- 注意:只指定 username 和 email,其他字段使用默认值
-- status 会使用默认值 1(启用)
-- created_at 会使用默认值 CURRENT_TIMESTAMP(当前时间)
INSERT INTO users (username, email) VALUES 
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com');

-- 验证插入结果
SELECT * FROM users;

-- ============================================
-- 2. SELECT(查询数据)
-- ============================================

-- 查询所有用户的所有字段
SELECT * FROM users;

-- 查询指定字段
-- 只查询 id、username、created_at 三个字段
SELECT id, username, created_at FROM users;

-- 查询活跃用户(status = 1)
-- WHERE 子句用于过滤数据
-- = 表示等于,只返回 status 为 1 的记录
SELECT id, username, created_at 
FROM users 
WHERE status = 1;

-- 查询特定用户
SELECT * FROM users WHERE username = 'Alice';

-- ============================================
-- 3. UPDATE(更新数据)
-- ============================================

-- ⚠️ 重要:UPDATE 必须加 WHERE 条件!
-- 不加 WHERE 会更新所有记录,这是非常危险的操作

-- 更新 Bob 的邮箱
-- 先查询确认要更新的记录
SELECT * FROM users WHERE username = 'Bob';

-- 执行更新
UPDATE users 
SET email = 'bob_new@example.com' 
WHERE username = 'Bob';

-- 验证更新结果
SELECT * FROM users WHERE username = 'Bob';

-- ============================================
-- 4. DELETE(删除数据)
-- ============================================

-- ⚠️ 重要:DELETE 必须加 WHERE 条件!
-- 不加 WHERE 会删除所有记录,这是灾难性错误!

-- 先查询确认要删除的记录
SELECT * FROM users WHERE username = 'Charlie';

-- 物理删除(不推荐在生产环境使用)
-- 注意:这里演示物理删除,实际生产环境建议使用软删除
-- 软删除:UPDATE users SET status = 0 WHERE username = 'Charlie';
DELETE FROM users WHERE username = 'Charlie';

-- 验证删除结果
SELECT * FROM users;
字段类型选择详解
危险示例
-- ❌ 错误:没有 WHERE 条件
UPDATE users SET email = 'hacked@example.com';
-- 结果:所有用户的邮箱都被修改!
正确做法
-- ✅ 正确:有 WHERE 条件
UPDATE users SET email = 'new@example.com' WHERE username = 'Alice';
-- 结果:只更新 Alice 的邮箱
最佳实践
  1. 更新前先用 SELECT 查询确认要更新的记录
  2. 在 WHERE 条件中使用唯一字段(如主键、用户名)
  3. 生产环境建议使用事务(后续课程会讲)
危险示例
-- ❌ 错误:没有 WHERE 条件
DELETE FROM users;
-- 结果:所有用户数据都被删除!无法恢复!
正确做法
-- ✅ 正确:有 WHERE 条件
DELETE FROM users WHERE username = 'Charlie';
-- 结果:只删除 Charlie 的记录
最佳实践
  1. 删除前先用 SELECT 查询确认要删除的记录
  2. 生产环境建议使用软删除(UPDATE status = 0)
  3. 重要数据删除前先备份
硬删除(物理删除)
  • 使用 DELETE FROM 命令
  • 数据从数据库中永久删除
  • 无法恢复
  • 适用于:测试数据、临时数据
软删除(逻辑删除)
  • 使用 UPDATE 命令修改状态字段
  • 数据仍然存在,只是标记为”已删除”
  • 可以恢复
  • 适用于:生产环境、重要数据
示例
-- 软删除:标记为禁用
UPDATE users SET status = 0 WHERE username = 'Charlie';

-- 查询时排除已删除的数据
SELECT * FROM users WHERE status = 1;

-- 恢复数据
UPDATE users SET status = 1 WHERE username = 'Charlie';
验证步骤
  1. 连接 MySQL:
    docker exec -it mysql-learn mysql -uroot -proot
    
  2. 执行 SQL 脚本:
    USE infra_db;
    source /path/to/02_crud.sql;
    
    或者直接复制粘贴 SQL 语句执行
  3. 验证 INSERT:
    SELECT * FROM users;
    -- 应该看到 3 条新插入的记录
    
  4. 验证 UPDATE:
    SELECT * FROM users WHERE username = 'Bob';
    -- 应该看到 Bob 的邮箱已更新
    
  5. 验证 DELETE:
    SELECT * FROM users;
    -- 应该看不到 Charlie 的记录
    
  6. 测试危险操作(仅限测试环境):
    -- 先备份数据
    CREATE TABLE users_backup AS SELECT * FROM users;
    
    -- 测试无 WHERE 的 UPDATE(危险!)
    -- UPDATE users SET email = 'test@example.com';
    -- 注意:不要在生产环境执行!
    
常见错误
  • UPDATE users SET email = 'new@example.com' - 没有 WHERE,会更新所有记录
  • DELETE FROM users - 没有 WHERE,会删除所有记录
  • INSERT INTO users VALUES (...) - 不指定字段,必须提供所有字段的值
  • SELECT * FROM users WHERE username = Alice - 字符串未加引号,会被当作列名
  • UPDATE users SET email = 'new@example.com' WHERE username = 'Alice' - 正确
  • DELETE FROM users WHERE username = 'Charlie' - 正确
  • INSERT INTO users (username, email) VALUES ('Alice', 'alice@example.com') - 正确
  • SELECT * FROM users WHERE username = 'Alice' - 正确

拓展任务 (30 mins)

Shell 挑战

任务:学习 while 循环,写一个脚本每秒输出当前时间,持续 5 秒后退出。提示
  • 使用 while 循环和计数器
  • 使用 date 命令获取当前时间
  • 使用 sleep 1 暂停 1 秒
  • 思考:如何让循环在 5 秒后自动退出?

SQL 挑战

任务:学习 LIMIT(分页)和 ORDER BY(排序)的用法。提示
  • 使用 ORDER BY created_at DESC 按创建时间倒序排列
  • 使用 LIMIT 10 限制返回 10 条记录
  • 思考:如何实现分页查询(第 1 页、第 2 页)?

今日产出物

  • day02/02_monitor.sh - Shell 监控脚本
  • day02/02_crud.sql - SQL CRUD 操作脚本

参考代码

查看参考代码

在 GitHub 查看完整的示例代码

在线运行

使用在线编辑器测试代码

实际应用场景

Shell 流程控制在运维中的应用

  • 服务监控:检查服务状态,异常时自动重启
  • 文件管理:批量处理日志文件,自动清理过期文件
  • 部署脚本:根据环境变量执行不同的部署流程
  • 备份脚本:循环备份多个数据库或目录
  • 健康检查:定期检查服务健康状态,发送告警

SQL CRUD 在生产环境的最佳实践

  • INSERT:使用批量插入提高效率,避免逐条插入
  • SELECT:只查询需要的字段,避免 SELECT *
  • UPDATE:必须加 WHERE 条件,更新前先查询确认
  • DELETE:生产环境使用软删除,保留数据可恢复
  • 事务管理:重要操作使用事务保证数据一致性(后续课程会讲)
与 Day 01 和 Day 03 的关联
  • Day 01 学习了基础的变量和表结构,今天学习了如何使用流程控制操作这些数据
  • Day 03 将学习更高级的 Shell 管道和 SQL 联表查询,今天的 CRUD 操作是基础
  • 掌握今天的流程控制和 CRUD,明天的学习会更轻松

上一天: 基础入门

Day 01 | Shell 基础入门与建表

下一天: 管道联表

Day 03 | Shell 管道与 SQL 联表查询