第一阶段:基础准备—— 先搞定 “Linux 环境” 和 “命令行习惯”
Shell 脚本和工具(grep/sed/awk)都依赖 Linux 命令行,先回顾核心 Linux 基础,避免后续 “卡壳”。
1.1 必须掌握的 Linux 基础(回顾 + 实验)
| 知识模块 | 核心内容 | 实战实验(跟着做) |
|---|---|---|
| Linux 文件系统 | 目录结构(/home、/etc、/var、/tmp)、绝对路径 vs 相对路径、文件权限(rwx) | 实验 1:创建多层目录并操作1. 执行 (创建多层目录)2. 执行 (绝对路径切换)3. 执行 (创建空文件)4. 执行 (修改权限为 “所有者可读可写可执行,其他只读可执行”)5. 执行 (查看权限是否生效,结果应显示 ) |
| 常用命令 | (切换目录)、(列文件)、(创文件)、(读文件)、(复制)、(移动 / 改名)、(删除)、(看文件末尾)、(看文件开头) |
实验 2:文件操作实战1. 执行 (创建日志文件,输入以下内容)(按回车结束输入)2. 执行 (查看第一行,应显示 “服务器启动成功”)3. 执行 (查看最后两行,应包含 “数据库连接失败” 和 “重启成功”)4. 执行 (备份日志)5. 执行 (把备份移到 /tmp 目录) |
| Shell 环境 | 默认 Shell( 查看,企业常用 bash)、环境变量( 配置个人环境)、命令帮助( 或 ) |
实验 3:配置 bash 环境1. 执行 (确认是否为 )2. 执行 (编辑 bash 配置文件,添加一行)(修改命令提示符为 “[用户名 @主机名 当前目录]$ ”)3. 执行 (让配置生效,此时命令提示符会立即变化)4. 执行 (查看 grep 的帮助文档,按 退出) |
1.2 学习工具准备
终端工具:Windows 用 Xshell、FinalShell;Mac 用自带终端或 iTerm2(方便复制命令、多窗口操作)。实验环境:推荐用虚拟机(VMware Workstation)装 CentOS 7,或直接买阿里云 / 腾讯云学生机(1 核 2G 足够,成本低,贴近真实服务器)。文本编辑器:(必须会基础操作:
vim 插入、
i 退出插入、
ESC 保存退出、
:wq 不保存退出)—— 后续写脚本全靠它。
:q!
第二阶段:正则表达式—— 所有工具的 “灵魂”,先吃透
grep/sed/awk 的核心是 “用正则匹配文本”,先学正则,再用工具时会 “一通百通”。
2.1 正则表达式基础(核心元字符)
正则是 “描述文本规则的符号”,比如 “匹配以数字开头的行”“匹配包含邮箱的字符串”,先记基础元字符:
| 元字符 | 作用说明 | 示例(匹配目标) |
|---|---|---|
|
匹配行的开头 | → 匹配以 “ERROR” 开头的行 |
|
匹配行的结尾 | → 匹配以 “success” 结尾的行 |
|
匹配任意 1 个字符(除了换行符) | → 匹配 “aab”“acb”“a1b”(中间任意 1 个) |
|
匹配前面的字符 “0 次或多次” | → 匹配 “b”“ab”“aab”“aaab” |
|
匹配 “中括号内的任意 1 个字符” | → 匹配任意 1 个数字; → 任意字母 |
|
匹配 “不在中括号内的任意 1 个字符”(取反) | → 匹配非数字字符 |
|
匹配前面的字符 “0 次或 1 次”(扩展正则,需加 选项) |
→ 匹配 “b” 或 “ab” |
|
匹配前面的字符 “1 次或多次”(扩展正则,需加 选项) |
→ 匹配 “ab”“aab”(至少 1 个 a) |
|
匹配前面的字符 “n 到 m 次”(扩展正则,需加 选项) |
→ 匹配 “aab”“aaab” |
|
匹配 “左边或右边的表达式”(扩展正则,需加 选项) |
→ 匹配 “ERROR” 或 “WARN” |
|
分组(把多个字符当一个整体,扩展正则,需加 选项) |
→ 匹配 “ab”“abab”“ababab” |
|
匹配数字(等价于 ,部分工具支持,如 grep) |
→ 匹配 4 个连续数字(如 “2024”) |
|
匹配字母 + 数字(等价于 ,通用字符类,避免中英文问题) |
→ 匹配连续的字母或数字 |
2.2 正则表达式实验(分基础→进阶)
先创建一个测试文件 ,后续所有正则实验都基于它:
regex_test.txt
bash
运行
# 执行以下命令创建文件
cat > regex_test.txt << EOF
1. 运维工程师的邮箱是 ops@example.com
2. 服务器IP:192.168.1.100,网关:192.168.1.1
3. 错误日志:ERROR: 数据库连接超时(2024-11-20 10:05)
4. 警告日志:WARN: 磁盘空间不足(剩余20%)
5. 正常日志:INFO: 服务启动成功(耗时123秒)
6. 无效行:@#$%^&*()_+
7. 多行测试:
第一行结尾
第二行中间有空格
EOF
实验 1:基础元字符练习(用 grep 验证,grep 默认支持基础正则)
匹配以 “ERROR” 开头的行:命令:预期结果:只显示第 3 行(
grep '^ERROR' regex_test.txt)解释:
3. 错误日志:ERROR: 数据库连接超时(2024-11-20 10:05) 锚定行首,只找开头是 ERROR 的行。
^
匹配以 “com” 结尾的行:命令:预期结果:只显示第 1 行(
grep 'com$' regex_test.txt)解释:
1. 运维工程师的邮箱是 ops@example.com 锚定行尾,只找结尾是 com 的行。
$
匹配包含 “IP:任意字符” 的行(用 匹配任意字符):命令:
.预期结果:显示第 2 行(
grep 'IP:.' regex_test.txt)解释:
2. 服务器IP:192.168.1.100,网关:192.168.1.1 匹配 “IP:” 后面的任意 1 个字符(这里是 “1”),所以能命中该行。
.
匹配包含 “数字 +%” 的行(用 匹配数字):命令:
[]预期结果:显示第 4 行(
grep '[0-9]%' regex_test.txt)解释:
4. 警告日志:WARN: 磁盘空间不足(剩余20%) 匹配任意数字,后面跟 “%”,所以命中 “20%”。
[0-9]
实验 2:扩展正则练习(用
grep -E,支持
?
+
|
())
grep -E
?
+
|
()
匹配包含 “ERROR” 或 “WARN” 的行(用 表示 “或”):命令:
|预期结果:显示第 3 行(ERROR)和第 4 行(WARN)解释:
grep -E 'ERROR|WARN' regex_test.txt 分隔两个关键词,只要包含其中一个就匹配。
|
匹配包含 “1-3 个数字 + 秒” 的行(用 控制次数):命令:
{n,m}预期结果:显示第 5 行(
grep -E '[0-9]{1,3}秒' regex_test.txt)解释:
5. 正常日志:INFO: 服务启动成功(耗时123秒) 匹配 1-3 个连续数字,后面跟 “秒”,所以命中 “123 秒”。
[0-9]{1,3}
匹配包含 “邮箱格式(xxx@xxx.xxx)” 的行(用分组 ):命令:
()预期结果:显示第 1 行(
grep -E '[a-z]+@[a-z]+.[a-z]+' regex_test.txt)解释:
1. 运维工程师的邮箱是 ops@example.com
:匹配 1 个以上小写字母(如 “ops”“example”“com”);
[a-z]+:匹配 @符号;
@:匹配小数点(
. 是元字符,需要用
. 转义,否则会匹配任意字符)。
第三阶段:核心工具实战—— grep→sed→awk,逐个突破
这三个工具是运维 “三剑客”,用途不同:
grep:专注 “文本搜索”(找符合条件的行);sed:专注 “文本编辑”(改、删、插行);awk:专注 “结构化数据处理”(按列分割、统计、计算)。
3.1 grep 工具(文本搜索之王)
3.1.1 grep 核心用法(选项 + 场景)
| 选项 | 作用说明 | 常用场景 | |
|---|---|---|---|
|
忽略大小写(匹配时不区分大写小写) | 搜索日志时,同时匹配 “Error”“ERROR” | |
|
反向匹配(只显示 “不满足条件” 的行) | 过滤日志中的空行() |
|
|
显示匹配行的 “行号” | 定位日志中错误行的位置 | |
|
只统计 “匹配行的数量”(不显示具体内容) | 统计错误日志有多少条 | |
|
递归搜索(遍历指定目录下的所有文件) | 搜索项目目录中包含 “密码” 的文件 | |
|
显示匹配行及其 “后面 n 行”(After) | 看错误行后面的上下文 | |
|
显示匹配行及其 “前面 n 行”(Before) | 看错误行前面的上下文 | |
|
显示匹配行及其 “前后 n 行”(Context) | 看错误行前后的上下文 | |
|
支持扩展正则(等价于 ) |
用复杂正则匹配 | |
|
把模式当作 “固定字符串”(不解析正则,等价于 ) |
搜索包含 “.”“*” 等元字符的字符串 |
3.1.2 grep 实战实验(结合运维场景)
先准备实验环境:创建 目录,生成两个日志文件
/var/log/ops/ 和
app.log:
db.log
bash
运行
# 1. 创建目录
mkdir -p /var/log/ops/
# 2. 生成 app.log(应用日志)
cat > /var/log/ops/app.log << EOF
2024-11-20 09:00:00 [INFO] App started (PID: 1234)
2024-11-20 09:05:00 [ERROR] Connection failed: timeout (IP: 10.0.0.5)
2024-11-20 09:10:00 [INFO] User 'admin' logged in
2024-11-20 09:15:00 [ERROR] File not found: /data/config.ini
2024-11-20 09:20:00 [WARN] Memory usage > 80%
EOF
# 3. 生成 db.log(数据库日志)
cat > /var/log/ops/db.log << EOF
2024-11-20 09:00:00 [INFO] MySQL started (Port: 3306)
2024-11-20 09:06:00 [ERROR] MySQL connection refused (User: root)
2024-11-20 09:10:00 [INFO] MySQL query success (Time: 0.2s)
EOF
实验 1:搜索单个文件中的错误日志(基础搜索)
需求:从 中找所有包含 “ERROR” 的行,并显示行号。命令:
app.log预期结果:
grep -n 'ERROR' /var/log/ops/app.log
plaintext
2:2024-11-20 09:05:00 [ERROR] Connection failed: timeout (IP: 10.0.0.5)
4:2024-11-20 09:15:00 [ERROR] File not found: /data/config.ini
解释: 显示行号,能快速定位错误在文件中的位置(第 2 行和第 4 行)。
-n
实验 2:统计所有日志中的错误数量(递归 + 统计)
需求:递归搜索 目录下所有文件,统计包含 “ERROR” 的总行数。命令:
/var/log/ops/预期结果:
grep -rc 'ERROR' /var/log/ops/
plaintext
/var/log/ops/app.log:2
/var/log/ops/db.log:1
解释: 递归遍历目录,
-r 统计每个文件的匹配行数,结果显示 app.log 有 2 个 ERROR,db.log 有 1 个。
-c
实验 3:过滤空行并查看错误上下文(反向匹配 + 上下文)
需求:从 中过滤掉空行(反向匹配),并查看 “ERROR” 行的前后 1 行上下文。命令:
app.log预期结果(以第一个 ERROR 为例):
grep -v '^$' /var/log/ops/app.log | grep -C 1 'ERROR'
plaintext
2024-11-20 09:00:00 [INFO] App started (PID: 1234)
2024-11-20 09:05:00 [ERROR] Connection failed: timeout (IP: 10.0.0.5)
2024-11-20 09:10:00 [INFO] User 'admin' logged in
--
2024-11-20 09:10:00 [INFO] User 'admin' logged in
2024-11-20 09:15:00 [ERROR] File not found: /data/config.ini
2024-11-20 09:20:00 [WARN] Memory usage > 80%
解释:
:过滤空行(
grep -v '^$' 匹配空行,
^$ 反向匹配,即保留非空行);
-v:管道符,把前一个命令的输出作为后一个命令的输入;
|:显示 ERROR 行的前后 1 行(
grep -C 1 'ERROR' 表示上下文 1 行),方便分析错误原因。
-C 1
实验 4:搜索固定字符串(避免正则解析)
需求:搜索包含 “[INFO]” 的行( 和
[ 是正则元字符,需当作普通字符处理)。命令:
]预期结果:显示所有包含 “[INFO]” 的行(第 1、3 行)解释:
grep -F '[INFO]' /var/log/ops/app.log 表示 “固定字符串匹配”,不解析正则元字符(如
-F
[),直接按字符串 “[INFO]” 搜索,避免匹配错误。
]
3.2 sed 工具(流编辑器,文本编辑之王)
sed 的核心是 “按行处理”:读取一行→按规则处理(改 / 删 / 插)→输出一行,不默认修改原文件(需加 才改)。
-i
3.2.1 sed 核心语法与命令
基本语法:常用选项:
sed [选项] '操作命令' 文件名
:抑制默认输出(只显示处理后的行,否则会输出所有行);
-n:直接修改原文件(危险!建议先不加
-i 测试,没问题再加);
-i:执行多个操作命令(多个命令用
-e 分隔);
-e:从脚本文件读取操作命令(命令多时用)。
-f
核心操作命令(重点记前 5 个):
| 命令 | 作用说明 | 示例(修改文件 test.txt) |
|---|---|---|
|
替换(substitute):把 “旧字符串” 换成 “新字符串”(默认只换每行第一个匹配) | → 把 ERROR 换成 WARN |
|
全局替换(g=global):换每行所有匹配的 “旧字符串” | → 每行所有 ERROR 都换 |
|
删除(delete):删除符合条件的行 | → 删除包含 ERROR 的行 |
|
打印(print):打印符合条件的行(常和 一起用) |
→ 只打印包含 INFO 的行 |
|
在符合条件的行 “后面” 插入(append)一行 “内容” | → 在 INFO 行后插行 |
|
在符合条件的行 “前面” 插入(insert)一行 “内容” | → 在 INFO 行前插行 |
|
用 “内容” 替换符合条件的 “整行”(change) | → 替换 ERROR 行 |
3.2.2 sed 实战实验(基于前面的
/var/log/ops/app.log)
/var/log/ops/app.log
实验 1:替换字符串(单行替换 + 全局替换)
需求 1:把 中 “ERROR” 换成 “ERR”(只换每行第一个 ERROR)。命令:
app.log预期结果(关键行):
sed 's/ERROR/ERR/' /var/log/ops/app.log
plaintext
2024-11-20 09:05:00 [ERR] Connection failed: timeout (IP: 10.0.0.5) # ERROR→ERR
2024-11-20 09:15:00 [ERR] File not found: /data/config.ini # ERROR→ERR
解释: 只替换每行第一个匹配的 ERROR(这里每行只有一个,效果和全局一样)。
s/ERROR/ERR/
需求 2:若文件中有 “ERROR ERROR”,需替换所有 ERROR 为 ERR(全局替换)。先构造测试文件:命令:
echo "ERROR ERROR" > test.txt预期结果:
sed 's/ERROR/ERR/g' test.txt(两个 ERROR 都被替换)解释:
ERR ERR 表示全局替换,每行所有匹配都换。
g
实验 2:删除行(删除空行、删除指定行)
需求 1:删除 中的空行(若有的话)。命令:
app.log预期结果:所有空行被删除,只显示非空行解释:
sed '/^$/d' /var/log/ops/app.log 匹配空行,
/^$/ 命令删除该行。
d
需求 2:删除 中包含 “WARN” 的行。命令:
app.log预期结果:原文件第 5 行(WARN 行)被删除,其他行保留解释:
sed '/WARN/d' /var/log/ops/app.log 匹配包含 WARN 的行,
/WARN/ 命令删除该行。
d
实验 3:插入行(在指定行前后插内容)
需求:在 中 “包含 INFO 的行” 后面插入一行 “——INFO 日志结束 ——”。命令:
app.log预期结果(关键行):
sed '/INFO/a——INFO日志结束——' /var/log/ops/app.log
plaintext
2024-11-20 09:00:00 [INFO] App started (PID: 1234)
——INFO日志结束——
2024-11-20 09:05:00 [ERROR] Connection failed: timeout (IP: 10.0.0.5)
2024-11-20 09:10:00 [INFO] User 'admin' logged in
——INFO日志结束——
解释: 匹配包含 INFO 的行,
/INFO/ 命令在该行后面插入指定内容。
a
实验 4:直接修改原文件(
sed -i,运维常用)
sed -i
需求:批量修改 中所有 “2024-11-20” 为 “2024-11-21”(日期更正)。步骤:
app.log
先测试修改效果(不加 ,看输出是否正确):命令:
-i预期:所有日期都变成 2024-11-21。
sed 's/2024-11-20/2024-11-21/g' /var/log/ops/app.log
确认正确后,加 修改原文件:命令:
-i
sed -i 's/2024-11-20/2024-11-21/g' /var/log/ops/app.log
验证修改结果:命令:预期:原文件中的日期已全部替换为 2024-11-21。注意:
cat /var/log/ops/app.log 会直接修改原文件,建议先备份(如
-i)再操作!
cp app.log app.log.bak
实验 5:批量修改文件名(结合
find 和
sed)
find
sed
需求:当前目录有
file1.txt
file2.txt,批量把 “file” 改成 “doc”(即
file3.txt 等)。步骤:
doc1.txt
先创建测试文件:
touch file1.txt file2.txt file3.txt
用 找文件,
find 改文件名:命令:
sed解释:
find . -name "file*.txt" | sed -n 's/(.*)file(.*)/mv & 1doc2/p' | sh
:找到当前目录下所有 file 开头的 txt 文件;
find . -name "file*.txt":
sed -n 's/(.*)file(.*)/mv & 1doc2/p'
:分组匹配,把文件名拆成 “前缀 + file + 后缀”(如 “./file1.txt” 拆成 “./” 和 “1.txt”);
(.*)file(.*):生成 mv 命令(
mv & 1doc2 表示原文件名,
& 是第一个分组,
1 是第二个分组,即
2);
mv ./file1.txt ./doc1.txt 和
-n:只打印生成的 mv 命令;
p
:执行生成的 mv 命令,完成文件名修改。
| sh
验证结果: → 显示
ls。
doc1.txt doc2.txt doc3.txt
3.3 awk 工具(结构化数据处理之王)
awk 擅长处理 “按列分割” 的数据(如日志、CSV 文件),核心是 “模式 + 动作”:对符合 “模式” 的行,执行 “动作”。
3.3.1 awk 核心概念与语法
1. 基础语法
awk '模式{动作}' 文件名
模式:可以是 “行号”(如 表示第 1 行)、“条件”(如
NR==1 表示第 3 列大于 100)、“BEGIN/END”(特殊模式,分别在处理文件前 / 后执行);动作:可以是 “打印”(
$3>100)、“计算”(
print)、“判断”(
sum+=1)等。
if($1=="ERROR")
2. 核心内置变量(必须记)
| 变量名 | 作用说明 | |
|---|---|---|
|
表示 “整行内容” | |
|
表示 “第 n 列内容”(列默认用 “空格或制表符” 分割,可通过 修改分隔符) |
|
|
输入字段分隔符(默认是空格,可在 BEGIN 中设置,如 表示用逗号分割) |
|
|
输出字段分隔符(默认是空格,如 `BEGIN {OFS=” | “}` 表示输出用竖线分隔) |
|
已读取的 “总行数”(遍历多个文件时,行数连续计数) | |
|
当前行的 “列数”(如一行有 3 列,) |
|
|
当前处理的 “文件名”(遍历多个文件时有用) |
3.3.2 awk 实战实验(结合运维场景:日志统计、数据计算)
先准备两个实验文件:
服务器监控日志 (按 “时间 服务器 IP CPU 使用率 内存使用率 磁盘使用率” 格式记录):
server_monitor.log
bash
运行
cat > server_monitor.log << EOF
2024-11-20 09:00 192.168.1.100 20% 30% 40%
2024-11-20 09:00 192.168.1.101 85% 70% 60%
2024-11-20 10:00 192.168.1.100 25% 35% 42%
2024-11-20 10:00 192.168.1.101 90% 75% 65%
2024-11-20 11:00 192.168.1.100 30% 40% 45%
2024-11-20 11:00 192.168.1.101 95% 80% 70%
EOF
CSV 格式的用户数据 (按 “用户名,UID,GID, 家目录,shell” 格式记录):
user.csv
bash
运行
cat > user.csv << EOF
root,0,0,/root,/bin/bash
bin,1,1,/bin,/sbin/nologin
daemon,2,2,/sbin,/sbin/nologin
admin,1000,1000,/home/admin,/bin/bash
test,1001,1001,/home/test,/bin/bash
EOF
实验 1:按列提取数据(基础打印)
需求 1:从 中提取 “服务器 IP” 和 “CPU 使用率”(第 2 列是 IP,第 3 列是 CPU)。命令:
server_monitor.log预期结果:
awk '{print $2, $3}' server_monitor.log
plaintext
192.168.1.100 20%
192.168.1.101 85%
192.168.1.100 25%
192.168.1.101 90%
192.168.1.100 30%
192.168.1.101 95%
解释: 是第 2 列(IP),
$2 是第 3 列(CPU),
$3 打印这两列,默认用空格分隔。
print $2, $3
需求 2:从 中提取 “用户名” 和 “家目录”(CSV 用逗号分隔,需修改
user.csv)。命令:
FS预期结果:
awk 'BEGIN{FS=","} {print $1, $4}' user.csv
plaintext
root /root
bin /bin
daemon /sbin
admin /home/admin
test /home/test
解释: 表示在处理文件前,把输入分隔符设为逗号(默认是空格),所以
BEGIN{FS=","} 是用户名,
$1 是家目录。
$4
实验 2:按条件过滤行(模式匹配)
需求 1:从 中找出 “CPU 使用率> 80%” 的行(第 3 列是 CPU,需去掉 “%” 再比较)。命令:
server_monitor.log预期结果:
awk '$3+0 > 80 {print $2, $3}' server_monitor.log
plaintext
192.168.1.101 85%
192.168.1.101 90%
192.168.1.101 95%
解释:
:把第 3 列(如 “85%”)转成数字(85),因为 “%” 会让内容变成字符串,无法直接比较;
$3+0:条件模式,只处理 CPU>80% 的行;
$3+0 > 80:打印这些行的 IP 和 CPU。
{print $2, $3}
需求 2:从 中找出 “shell 是 /bin/bash” 的用户(第 5 列是 shell,用逗号分隔)。命令:
user.csv预期结果:
awk 'BEGIN{FS=","} $5=="/bin/bash" {print $1, $5}' user.csv
plaintext
root /bin/bash
admin /bin/bash
test /bin/bash
解释: 是条件模式,只处理第 5 列(shell)为 /bin/bash 的行,然后打印用户名和 shell。
$5=="/bin/bash"
实验 3:统计数据(计算求和、平均值)
需求:统计 中 192.168.1.101 的 “平均 CPU 使用率”。命令:
server_monitor.log预期结果:
awk '$2=="192.168.1.101" {sum+=$3+0; count++} END{print "平均CPU使用率:", sum/count "%"}' server_monitor.log解释:
平均CPU使用率: 90%
:只处理 IP 是 101 的行;
$2=="192.168.1.101":累加 CPU 使用率(转成数字);
sum+=$3+0:统计符合条件的行数(共 3 行);
count++:处理完所有行后执行,计算平均值(sum/count= (85+90+95)/3=90)并打印。
END{...}
实验 4:格式化输出(
printf,比
print更灵活)
printf
需求:从 中提取用户信息,按 “用户名:root,UID:0” 的格式整齐输出。命令:
user.csv预期结果:
awk 'BEGIN{FS=","; printf "%-10s %-5s
", "用户名", "UID"} {printf "%-10s %-5d
", $1, $2}' user.csv
plaintext
用户名 UID
root 0
bin 1
daemon 2
admin 1000
test 1001
解释:
:格式化输出:
printf "%-10s %-5s
"
:左对齐(
%-10s 表示左对齐),占 10 个字符位,字符串类型(
-);
s:左对齐,占 5 个字符位,数字类型(
%-5d);
d
:换行;
中先打印表头(用户名、UID),再处理每行数据,输出更整齐。
BEGIN
实验 5:处理日志中的 IP 访问次数(运维高频需求)
需求:假设有访问日志 (格式:
access.log),统计每个 IP 的访问次数,按次数降序排列。步骤:
IP 访问时间 访问路径
创建 :
access.log
bash
运行
cat > access.log << EOF
192.168.1.100 2024-11-20 09:00 /index.html
192.168.1.100 2024-11-20 09:05 /login.html
192.168.1.101 2024-11-20 09:10 /index.html
192.168.1.100 2024-11-20 09:15 /user.html
192.168.1.102 2024-11-20 09:20 /index.html
192.168.1.101 2024-11-20 09:25 /logout.html
EOF
用 awk 统计 IP 次数,sort 排序:命令:预期结果:
awk '{ip[$1]++} END{for(i in ip) print i, ip[i]}' access.log | sort -k2nr
plaintext
192.168.1.100 3
192.168.1.101 2
192.168.1.102 1
解释:
:定义关联数组
ip[$1]++,以 IP(
ip)为索引,每遇到一个 IP 就加 1(统计次数);
$1:遍历数组,打印 IP 和对应的次数;
END{for(i in ip) print i, ip[i]}:按第 2 列(次数)降序排列(
sort -k2nr 表示按第 2 列,
-k2 表示数字排序,
n 表示降序)。
r
第四阶段:Shell 脚本编程—— 把工具串联成 “自动化脚本”
运维工作中,很少单独用 grep/sed/awk,而是用 Shell 脚本把它们串联起来,实现 “自动化任务”(如定时备份、系统监控、批量部署)。
4.1 Shell 脚本基础(语法 + 规范)
4.1.1 脚本开头与执行方式
脚本开头必须加 “解释器路径”:(告诉系统用 bash 执行脚本);脚本命名:建议以
#!/bin/bash 结尾(如
.sh),方便识别;执行方式:
backup.sh
赋予执行权限:,然后执行
chmod +x script.sh(绝对 / 相对路径);直接用 bash 执行:
./script.sh(无需执行权限)。
bash script.sh
示例:创建第一个脚本
hello.sh
bash
运行
# 1. 用vim创建脚本
vim hello.sh
# 2. 输入以下内容
#!/bin/bash
# 这是注释:第一个Shell脚本
echo "Hello, Shell!" # 打印内容
# 3. 赋予执行权限并执行
chmod +x hello.sh
./hello.sh
# 预期结果:输出 "Hello, Shell!"
4.1.2 核心语法(变量、流程控制、函数)
1. 变量(无类型,直接定义)
| 语法 | 说明 | 示例 |
|---|---|---|
| 定义变量 | (等号前后无空格!) |
→ 定义变量 name |
| 使用变量 | 或 (推荐用 ,避免歧义) |
→ 输出 Linux; → 输出 LinuxOps |
| 位置参数 | 表示第一个参数, 第二个,…, 第 n 个; 所有参数; 参数个数 |
执行 → , |
| 环境变量 | 系统预定义变量(大写):(家目录)、(命令路径)、(上命令退出码,0 = 成功,非 0 = 失败) |
→ 输出 /root(root 用户) |
实验:变量使用脚本
var_demo.sh
bash
运行
#!/bin/bash
# 位置参数示例
echo "脚本名:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "所有参数:$*"
echo "参数个数:$#"
# 自定义变量
name="运维工程师"
age=30
echo "姓名:${name},年龄:${age}"
# 环境变量
echo "家目录:$HOME"
echo "上命令退出码:$?" # 上一个echo命令成功,所以输出0
执行:预期结果:
./var_demo.sh 北京 2024
plaintext
脚本名:./var_demo.sh
第一个参数:北京
第二个参数:2024
所有参数:北京 2024
参数个数:2
姓名:运维工程师,年龄:30
家目录:/root
上命令退出码:0
2. 流程控制(if-else、for、while)
(1)if-else 条件判断
语法:
bash
运行
if [ 条件 ]; then
执行命令1
elif [ 条件2 ]; then
执行命令2
else
执行命令3
fi
条件判断注意:
中,括号前后必须有空格;数值比较:
[ 条件 ](等于)、
-eq(不等于)、
-ne(大于)、
-gt(小于)、
-lt(大于等于)、
-ge(小于等于);字符串比较:
-le(等于)、
==(不等于)、
!=(空字符串)、
-z(非空字符串);文件判断:
-n(是否为文件)、
-f(是否为目录)、
-d(是否可读)、
-r(是否可写)、
-w(是否可执行)。
-x
实验:系统磁盘监控脚本 (当磁盘使用率 > 80% 时报警)
disk_monitor.sh
bash
运行
#!/bin/bash
# 监控根目录磁盘使用率
disk_usage=$(df -h / | grep '/' | awk '{print $5}' | sed 's/%//') # 提取根目录使用率(数字)
if [ $disk_usage -gt 80 ]; then
echo "【报警】根目录磁盘使用率已超过80%,当前使用率:${disk_usage}%"
# 实际工作中可加邮件/短信报警(如用mail命令)
elif [ $disk_usage -ge 60 ]; then
echo "【警告】根目录磁盘使用率已超过60%,当前使用率:${disk_usage}%"
else
echo "【正常】根目录磁盘使用率:${disk_usage}%"
fi
执行:预期结果(根据实际磁盘使用率输出,如使用率 50% 时):
./disk_monitor.sh
【正常】根目录磁盘使用率:50%
(2)for 循环(遍历列表或范围)
语法 1(遍历列表):
bash
运行
for 变量 in 列表; do
执行命令
done
语法 2(遍历数字范围):
bash
运行
for ((i=1; i<=10; i++)); do
执行命令
done
实验 1:批量创建用户脚本 (创建 user1-user5,密码 123456)
create_users.sh
bash
运行
#!/bin/bash
# 批量创建用户
for user in user1 user2 user3 user4 user5; do
# 先判断用户是否存在
if id -u $user &>/dev/null; then
echo "用户 $user 已存在,跳过创建"
else
useradd $user # 创建用户
echo "123456" | passwd --stdin $user &>/dev/null # 设置密码(&>/dev/null 屏蔽输出)
echo "用户 $user 创建成功,密码:123456"
fi
done
执行(需 root 权限):预期结果:
sudo ./create_users.sh
plaintext
用户 user1 创建成功,密码:123456
用户 user2 创建成功,密码:123456
...
实验 2:遍历数字范围,打印 1-5 的平方
bash
运行
#!/bin/bash
for ((i=1; i<=5; i++)); do
square=$((i*i)) # 计算平方($((...)) 是算术运算)
echo "${i}的平方是:${square}"
done
执行:预期结果:
./square.sh
plaintext
1的平方是:1
2的平方是:4
3的平方是:9
4的平方是:16
5的平方是:25
(3)while 循环(条件为真时执行)
语法:
bash
运行
while [ 条件 ]; do
执行命令
done
实验:倒计时脚本 (从 10 秒倒计时到 0)
countdown.sh
bash
运行
#!/bin/bash
# 10秒倒计时
sec=10
while [ $sec -ge 0 ]; do
echo -ne "倒计时:${sec}秒
" # -ne:不换行;
:光标回到行首(覆盖当前行)
sleep 1 # 暂停1秒
sec=$((sec-1)) # 秒数减1
done
echo -e "
倒计时结束!"
执行:预期结果:屏幕显示从 10 到 0 的倒计时,每秒更新一次,最后输出 “倒计时结束!”。
./countdown.sh
3. 函数(封装重复代码)
语法:
bash
运行
# 定义函数
函数名() {
执行命令(可接收参数,用$1/$2...获取)
return 退出码 # 可选,默认返回最后一个命令的退出码
}
# 调用函数
函数名 参数1 参数2...
实验:包含函数的备份脚本 (封装备份逻辑,支持备份不同目录)
backup_func.sh
bash
运行
#!/bin/bash
# 定义备份函数
backup_dir() {
local src_dir=$1 # 局部变量(只在函数内有效)
local dest_dir="/backup" # 备份目标目录
local backup_name="backup_$(basename $src_dir)_$(date +%Y%m%d_%H%M%S).tar.gz" # 备份文件名(含日期)
# 检查源目录是否存在
if [ ! -d $src_dir ]; then
echo "错误:源目录 $src_dir 不存在!"
return 1 # 返回错误码1
fi
# 创建目标目录(不存在则创建)
[ ! -d $dest_dir ] && mkdir -p $dest_dir
# 执行备份(tar打包压缩)
tar -zcf ${dest_dir}/${backup_name} $src_dir &>/dev/null
if [ $? -eq 0 ]; then
echo "备份成功:${dest_dir}/${backup_name}"
return 0
else
echo "备份失败:$src_dir"
return 1
fi
}
# 调用函数(备份/etc和/home目录)
backup_dir "/etc"
backup_dir "/home"
执行(需 root 权限,因为备份 /etc 和 /home):预期结果:
sudo ./backup_func.sh
plaintext
备份成功:/backup/backup_etc_20241120_153000.tar.gz
备份成功:/backup/backup_home_20241120_153005.tar.gz
4.2 Shell 脚本实战(运维高频场景)
实战 1:定时日志备份脚本(结合 crontab)
需求:每天凌晨 2 点,自动备份 到
/var/log/nginx/access.log,并删除 7 天前的旧备份。
/data/log_backup/
步骤 1:编写脚本
nginx_log_backup.sh
bash
运行
#!/bin/bash
# Nginx访问日志备份脚本
LOG_SRC="/var/log/nginx/access.log" # 源日志文件
BACKUP_DIR="/data/log_backup" # 备份目录
DATE=$(date +%Y%m%d_%H%M%S) # 当前日期(如20241120_020000)
BACKUP_FILE="nginx_access_${DATE}.tar.gz" # 备份文件名
# 1. 检查源日志是否存在
if [ ! -f $LOG_SRC ]; then
echo "错误:日志文件 $LOG_SRC 不存在!" >&2 # 错误信息输出到标准错误
exit 1
fi
# 2. 创建备份目录
mkdir -p $BACKUP_DIR
# 3. 备份日志(先打包,再清空原日志,避免日志过大)
tar -zcf ${BACKUP_DIR}/${BACKUP_FILE} $LOG_SRC &>/dev/null
if [ $? -eq 0 ]; then
echo "$(date +'%Y-%m-%d %H:%M:%S') 备份成功:${BACKUP_DIR}/${BACKUP_FILE}" >> /var/log/backup.log # 记录备份日志
> $LOG_SRC # 清空原日志(或用 logrotate,企业中更常用)
else
echo "$(date +'%Y-%m-%d %H:%M:%S') 备份失败:$LOG_SRC" >> /var/log/backup.log
exit 1
fi
# 4. 删除7天前的旧备份(find查找7天前的文件并删除)
find $BACKUP_DIR -name "nginx_access_*.tar.gz" -mtime +7 -delete
if [ $? -eq 0 ]; then
echo "$(date +'%Y-%m-%d %H:%M:%S') 删除7天前的旧备份成功" >> /var/log/backup.log
else
echo "$(date +'%Y-%m-%d %H:%M:%S') 删除旧备份失败" >> /var/log/backup.log
fi
步骤 2:赋予执行权限并测试
bash
运行
chmod +x nginx_log_backup.sh
sudo ./nginx_log_backup.sh # 测试执行,检查是否生成备份文件
步骤 3:添加定时任务(crontab)
bash
运行
# 编辑crontab配置(root用户,因为要操作/var/log和/data)
sudo crontab -e
# 添加以下内容(每天凌晨2点执行)
0 2 * * * /path/to/nginx_log_backup.sh # 替换/path/to为脚本实际路径
# 查看crontab状态(确保crond服务在运行)
sudo systemctl status crond # CentOS
# 或
sudo systemctl status cron # Ubuntu
实战 2:服务器批量执行命令脚本(结合 ssh 免密登录)
需求:批量在 10 台服务器上执行 “查看 CPU 使用率”“重启 nginx 服务” 等命令(前提:已配置 ssh 免密登录,即本地公钥已上传到目标服务器)。
步骤 1:准备服务器列表文件 (每行一个 IP)
server_list.txt
bash
运行
cat > server_list.txt << EOF
192.168.1.100
192.168.1.101
192.168.1.102
# 此处省略其他7台服务器IP
EOF
步骤 2:编写批量执行脚本
batch_exec.sh
bash
运行
#!/bin/bash
# 服务器批量执行命令脚本
SERVER_LIST="server_list.txt" # 服务器列表文件
USER="root" # 登录用户名(建议用普通用户+sudo,更安全)
COMMAND=$1 # 要执行的命令(通过位置参数传入)
# 检查命令是否传入
if [ $# -eq 0 ]; then
echo "用法:$0 <要执行的命令>"
echo "示例:$0 'top -bn1 | grep Cpu' (查看CPU使用率)"
echo "示例:$0 'systemctl restart nginx' (重启nginx)"
exit 1
fi
# 检查服务器列表文件是否存在
if [ ! -f $SERVER_LIST ]; then
echo "错误:服务器列表文件 $SERVER_LIST 不存在!"
exit 1
fi
# 遍历服务器列表,执行命令
while read server; do
# 跳过空行和注释行
if [ -z "$server" ] || [[ $server == #* ]]; then
continue
fi
echo -e "
==================== 服务器 $server 执行结果 ===================="
# 用ssh登录服务器执行命令
ssh -o StrictHostKeyChecking=no $USER@$server "$COMMAND" # -o StrictHostKeyChecking=no:首次登录不提示确认
if [ $? -eq 0 ]; then
echo "命令执行成功"
else
echo "命令执行失败"
fi
done < $SERVER_LIST # 从服务器列表文件读取内容,传给while循环
步骤 3:配置 ssh 免密登录(关键前提)
bash
运行
# 1. 本地生成ssh密钥对(已生成则跳过)
ssh-keygen -t rsa # 一路按回车,不设置密码
# 2. 批量上传公钥到目标服务器(需输入目标服务器密码,只输一次)
while read server; do
if [ -z "$server" ] || [[ $server == #* ]]; then
continue
fi
ssh-copy-id $USER@$server # 输入目标服务器密码
done < $SERVER_LIST
步骤 4:执行批量命令
bash
运行
# 赋予脚本执行权限
chmod +x batch_exec.sh
# 示例1:批量查看所有服务器的CPU使用率
./batch_exec.sh 'top -bn1 | grep Cpu'
# 示例2:批量重启所有服务器的nginx服务
./batch_exec.sh 'systemctl restart nginx'




















暂无评论内容