Linux文本处理
1. 正则表达式
正则表达式(Regex 或 RegExp)是一种强大的文本模式描述语言。它使用一系列特殊字符(元字符)和普通字符来定义一个搜索模式,然后可以用这个模式去匹配、查找、替换文本。在处理日志、数据清洗、脚本编程中,正则表达式无处不在。
1.1 什么是正则表达式
想象一下,你想在文件中查找所有符合特定格式的电话号码、邮箱地址或者错误日志。如果只用简单的字符串查找,会非常困难且不精确。正则表达式提供了一种通用的、精确描述这些“模式”的方法。
- 核心思想: 定义规则,匹配符合规则的字符串。
- 应用场景:
- 文本搜索与查找 (
grep) - 文本替换与编辑 (
sed,vim, 各种编程语言) - 数据验证 (例如,校验用户输入的格式是否正确)
- 数据提取 (例如,从日志中提取特定信息)
- 文本搜索与查找 (
1.2 基本元字符
元字符是正则表达式中有特殊含义的字符,它们不代表自身,而是用于构建匹配规则。
| 元字符 | 说明 | 示例 (匹配项加粗) |
|---|---|---|
. |
匹配其所在位置上除换行符 \n 之外的任意单个字符。 |
gr.y 匹配 gray, grey, grxy |
* |
量词:匹配其前面的字符出现零次或多次 (0, ∞)。 | go*d 匹配 gd, god, good, gooood |
+ |
量词:匹配其前面的字符出现一次或多次 (1, ∞)。 | go+d 匹配 god, good, gooood (不匹配 gd) |
? |
量词:匹配其前面的字符出现零次或一次 (0, 1)。 | colou?r 匹配 color, colour |
{n} |
量词:匹配其前面的字符 精确出现 n 次。 |
go{2}d 只匹配 good |
{n,} |
量词:匹配其前面的字符 至少出现 n 次 (n, ∞)。 |
go{2,}d 匹配 good, gooood |
{n,m} |
量词:匹配其前面的字符出现至少 n 次,最多 m 次 (n, m)。 |
go{1,2}d 匹配 god, good |
^ |
锚点:匹配行首。 | ^start 匹配以 start 开头的行 |
$ |
锚点:匹配行尾。 | end$ 匹配以 end 结尾的行 |
[] |
字符集:匹配方括号中任意一个字符。 | gr[ae]y 匹配 gray, grey |
[^] |
否定字符集:匹配不在方括号中的任意一个字符。 | [^0-9] 匹配任意非数字字符 |
[-] |
范围表示:在字符集中,[a-z] 匹配所有小写字母, [0-9] 匹配所有数字。 |
[a-zA-Z0-9] 匹配任意字母或数字 |
\ |
转义字符:将元字符转义为其字面含义,或将普通字符转义为特殊含义。 | \. 匹配字符 . 本身; \* 匹配 * 本身; \d 匹配数字 |
() |
分组:将多个字符组合成一个单元,可以应用量词,也可以用于捕获。 | (log)? 匹配 log 或空字符串; (ab)+ 匹配 ab, abab |
| |
或 | (ab)|(xyz) |
1.3 常用字符类
| 字符类 | 说明 | 等效于 |
|---|---|---|
\d |
匹配任意一个数字 | [0-9] |
\D |
匹配任意一个非数字字符 | [^0-9] |
\w |
匹配字母、数字或下划线 | [a-zA-Z0-9_] |
\W |
匹配非字母、数字、下划线 | [^a-zA-Z0-9_] |
\s |
匹配任意空白字符 (空格, tab, 换行等) | [ \t\r\n\f\v] |
\S |
匹配任意非空白字符 | [^ \t\r\n\f\v] |
1.4 贪婪与非贪婪匹配
默认情况下,量词 *, +, {n,} 是贪婪的 (Greedy),它们会尽可能多地匹配字符。在量词后加上 ? 可以使其(量词)变为非贪婪 (Non-Greedy) 或 懒惰 (Lazy) 模式,尽可能少地匹配字符。
示例:
1
2
# 数据样本
abbbbbc
ab*(贪婪): 匹配 abbbbb (b*匹配了所有5个b)ab*?(非贪婪): 匹配 a (b*?匹配了0个b)ab+(贪婪): 匹配 abbbbb (b+匹配了所有5个b)ab+?(非贪婪): 匹配 ab (b+?只匹配了1个b)
1.5 示例:构建金融场景正则
匹配中国大陆手机号 (简易版):
1
2
# 13813800000
^1[3-9]\d{9}$
^: 行首1: 以数字 1 开头[3-9]: 第二位是 3 到 9 中的一个数字\d{9}: 后面跟 9 个数字$: 行尾
匹配银行卡号 (假设16或19位数字):
1
^\d{16}(\d{3})?$
^: 行首\d{16}: 匹配 16 个数字(\d{3})?: 可选地 (?) 匹配一个包含 3 个数字 (\d{3}) 的分组$: 行尾
匹配 YYYY-MM-DD 格式日期:
1
^\d{4}-\d{2}-\d{2}$
\d{4}: 4位年份-: 分隔符\d{2}: 2位月份-: 分隔符\d{2}: 2位日期
匹配日志中 ERROR 或 WARN 级别的消息:
1
(ERROR|WARN)
()和|用于分组和或逻辑。
2. 文本处理三剑客
为什么文本处理如此重要?
- 数据普遍性: 在 Linux 和大数据生态中,大量的数据和信息以文本形式存在,例如:
- 系统日志 (
/var/log/messages,/var/log/secure等) - 应用程序日志 (Tomcat, Nginx, Hadoop, Spark 等服务的日志)
- 配置文件 (XML, YAML, properties 文件等)
- 数据文件 (CSV, TSV, JSON Lines 等)
- 脚本文件 (Shell, Python 等)
- 命令输出
- 系统日志 (
- 核心任务: 从这些海量的文本信息中快速、准确地提取、过滤、转换所需内容,是数据处理、系统运维、问题排查的基础。例如:
- 从日志中找出错误信息。
- 从配置文件中提取特定参数的值。
- 从 CSV 数据中筛选出符合条件的行。
- 统计某个事件发生的次数。
Linux 文本处理“三剑客”:
grep(Global Regular Expression Print): 主要用于查找和过滤。它逐行读取输入,打印出匹配指定模式(Pattern)的行。sed(Stream Editor): 主要用于编辑和转换。它也是逐行处理,可以对匹配模式的行执行替换、删除、插入等操作。是非交互式的文本编辑器。awk(Aho, Weinberger, Kernighan): 主要用于格式化报告和按列处理。它将每一行视为由字段(列)组成,可以方便地提取、计算、格式化输出特定字段。是一种强大的文本扫描和处理语言。- 三者结合: 它们经常通过
管道 (|)组合使用,形成强大的文本处理流水线。例如,先用grep筛选出包含错误的行,再用awk提取其中的关键字段。
2.1 文本搜索-grep
grep (Global Regular Expression Print) 是一个强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
2.1.1 grep基本使用
基本语法:
1
2
3
grep [OPTIONS] PATTERN [FILE...]
或者
command | grep [选项] PATTERN
OPTIONS: 控制grep行为的选项 (例如-i,-v,-n,-c,-w,-l,-r),无此选项表示只过滤PATTERN指定的内容或模式。PATTERN: 你要搜索的模式,可以是普通字符串,也可以是正则表达式。FILE...: 要搜索的文件名,可以是一个或多个。如果省略,grep会从标准输入读取数据(常用于管道)。
常用选项(OPTIONS):
| 选项 | 描述 | 示例 (假设文件 data.log) |
|---|---|---|
-i |
忽略大小写 (Ignore case) | grep -i 'error' data.log |
-v |
反向查找,只打印不匹配的行 (Invert match) | grep -v 'debug' data.log |
-n |
显示匹配行的行号 (Line number) | grep -n 'warning' data.log |
-c |
只输出匹配的行数 (Count) | grep -c 'success' data.log |
-w |
匹配整个单词 (Word regexp) | grep -w 'login' data.log (不会匹配 logins) |
-l |
只列出包含匹配项的文件名 (List files) | grep -l 'fail' *.log |
-L |
只列出不包含匹配项的文件名 | grep -L 'fail' *.log |
-r or -R |
递归搜索子目录 (Recursive) | grep -r 'critical' /var/log/ |
-E |
使用扩展正则表达式 (Extended regexp) | grep -E '正则表达式' data.log |
-o |
只显示匹配到的字符串部分 (Only matching) | grep -o '[0-9]+' data.log |
-A num |
显示匹配行及其后 num 行 (After context) | grep -A 2 'exception' data.log |
-B num |
显示匹配行及其前 num 行 (Before context) | grep -B 3 'traceback' data.log |
-C num |
显示匹配行及其前后各 num 行 (Context) | grep -C 1 'segmentation fault' data.log |
--color=auto |
高亮显示匹配的关键词 (通常是默认或别名) | grep --color=auto 'denied' data.log |
准备示例数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 创建一个工作目录,并进入到该目录
mkdir -p /root/grep_case && cd /root/grep_case
# 创建一个示例日志文件 data.log
cat << EOF > data.log
[2025-04-05 22:30:01] INFO: User 'alice' logged in successfully. Session ID: SESS001
[2025-04-05 22:31:15] WARN: Disk space low on /dev/sda1. Usage: 95%
[2025-04-05 22:32:05] INFO: Transaction processed. ID: TXN1001, Amount: 500.00
[2025-04-05 22:33:10] ERROR: Failed to connect to database 'order_db'. Code: DB500
[2025-04-05 22:33:12] INFO: User 'bob' logged in successfully. Session ID: SESS002
[2025-04-05 22:34:00] DEBUG: Checking cache for key 'user:bob'
[2025-04-05 22:35:05] ERROR: NullPointerException in PaymentService.java:150
[2025-04-05 22:36:10] INFO: User 'alice' updated profile.
[2025-04-05 22:37:15] WARN: High CPU usage detected. Load: 5.5
[2025-04-05 22:38:00] INFO: Batch job 'report_gen' completed.
[2025-04-05 22:38:05] error: Payment failed for user 'charlie'. Reason: Insufficient funds.
EOF
# 创建另一个文件 other.log
cat << EOF > other.log
System check OK.
All services running.
No critical errors found.
EOF
实操练习:
-
查找包含 “ERROR” 的行 (区分大小写):
1
grep 'ERROR' data.log
-
预期输出:
1 2
[2025-04-05 22:33:10] ERROR: Failed to connect to database 'order_db'. Code: DB500 [2025-04-05 22:35:05] ERROR: NullPointerException in PaymentService.java:150
-
-
查找包含 “error” 的行 (忽略大小写):
1
grep -i 'error' data.log
-
预期输出:
1 2 3
[2025-04-05 22:33:10] ERROR: Failed to connect to database 'order_db'. Code: DB500 [2025-04-05 22:35:05] ERROR: NullPointerException in PaymentService.java:150 [2025-04-05 22:38:05] error: Payment failed for user 'charlie'. Reason: Insufficient funds.
-
-
查找不包含 “INFO” 的行:
1
grep -v 'INFO' data.log
-
预期输出:
1 2 3 4 5 6
[2025-04-05 22:31:15] WARN: Disk space low on /dev/sda1. Usage: 95% [2025-04-05 22:33:10] ERROR: Failed to connect to database 'order_db'. Code: DB500 [2025-04-05 22:34:00] DEBUG: Checking cache for key 'user:bob' [2025-04-05 22:35:05] ERROR: NullPointerException in PaymentService.java:150 [2025-04-05 22:37:15] WARN: High CPU usage detected. Load: 5.5 [2025-04-05 22:38:05] error: Payment failed for user 'charlie'. Reason: Insufficient funds.
-
-
查找包含 “logged in” 的行,并显示行号:
1
grep -n 'logged in' data.log
-
预期输出:
1 2
1:[2025-04-05 22:30:01] INFO: User 'alice' logged in successfully. Session ID: SESS001 5:[2025-04-05 22:33:12] INFO: User 'bob' logged in successfully. Session ID: SESS002
-
-
统计包含 “WARN” 的行数:
1
grep -c 'WARN' data.log
-
预期输出:
1
2
-
-
查找包含单词 “error” 的行 (注意与 -i error 的区别):
1
grep -w 'error' data.log
-
预期输出: (只会匹配小写的
error这个独立的单词)1
[2025-04-05 22:38:05] error: Payment failed for user 'charlie'. Reason: Insufficient funds.
-
-
列出当前目录下哪些 .log 文件包含 “fail”:
1
grep -l 'failed' *.log
-
预期输出:
1
data.log
-
-
查找包含 “Exception” 的行,并显示其后 1 行:
1
grep -A 1 'Exception' data.log
-
预期输出:
1 2
[2025-04-05 22:35:05] ERROR: NullPointerException in PaymentService.java:150 [2025-04-05 22:36:10] INFO: User 'alice' updated profile.
-
-
从 ls -l 的输出中查找属于 bigdata 用户的文件:
1
ls -l | grep 'bigdata'
- 说明: 这里
grep从管道 (|) 读取ls -l命令的标准输出。
- 说明: 这里
2.1.2 grep与正则表达式
正则表达式是用于匹配一系列符合某个句法规则的字符串的模式。grep 支持两种主要的正则表达式方言:
- 基本正则表达式 (Basic Regular Expressions, BRE): 默认模式。一些元字符转义后才具有特殊含义,如:
?,+,{,|,(需要用反斜杠\转义后才具有特殊含义。 -
扩展正则表达式 (Extended Regular Expressions, ERE): 通过 -E选项启用。元字符通常不需要反斜杠转义,语法更简洁。推荐学习和使用 ERE。扩展正则多了 ` + ? {} ()`
常用正则表达式元字符 (ERE):
| 元字符 | 描述 | 示例 (ERE: grep -E ‘…’) | |
|---|---|---|---|
. |
匹配除换行符外的任意单个字符 | grep -E 'l.g' data.log (匹配 log, lig 等) |
|
* |
匹配前面的字符零次或多次 | grep -E 'SESS[0-9]*' data.log (匹配 SESS, SESS0, SESS001) |
|
+ |
匹配前面的字符一次或多次 | grep -E 'SESS[0-9]+' data.log (匹配 SESS0, SESS001, 不匹配 SESS) |
|
? |
匹配前面的字符零次或一次 | grep -E 'errors?' data.log (匹配 error, errors) |
|
{n} |
匹配前面的字符恰好 n 次 | grep -E '[0-9]{3}' data.log (匹配 3 位数字) |
|
{n,} |
匹配前面的字符至少 n 次 | grep -E '[0-9]{4,}' data.log (匹配至少 4 位数字) |
|
{n,m} |
匹配前面的字符至少 n 次,至多 m 次 | grep -E '[0-9]{2,4}' data.log (匹配 2 到 4 位数字) |
|
^ |
匹配行首 | grep -E '^\[2025' data.log (匹配以 [2025 开头的行) |
|
$ |
匹配行尾 | grep -E '%$' data.log (匹配以 % 结尾的行) |
|
[] |
字符集,匹配方括号内的任意一个字符 | grep -E '[aeiou]' data.log (匹配包含元音字母的行) |
|
[^] |
否定字符集,匹配任意不在方括号内的字符 | grep -E '[^0-9]' data.log (匹配包含非数字字符的行) |
|
[a-z] |
范围,匹配 a 到 z 的任意一个小写字母 | grep -E 'User ''[a-z]+''' data.log (匹配用户名) |
|
[A-Z] |
匹配任意大写字母 | ||
[0-9] |
匹配任意数字 | grep -E 'Amount: [0-9.]+' data.log (匹配金额) |
|
| ` | ` | 或操作符 | |
() |
分组,将多个字符视为一个单元,可用于限定范围或捕获 | grep -E '(error)' data.log |
|
\ |
转义字符,用于匹配元字符本身或表示特殊序列 | grep -E '\.' data.log (匹配实际的点 .) |
|
\w |
匹配字母、数字、下划线 (等同于 [a-zA-Z0-9_]) |
grep -E '\w+' data.log |
|
\d |
匹配数字 (等同于 [0-9]) |
grep -E '\d{2}:\d{2}:\d{2}' data.log (匹配时间格式) |
|
\s |
匹配空白字符 (空格, tab 等) | grep -E 'Disk\s+space' data.log |
实操练习 (使用 ERE):
-
查找包含 3 位数字的行:
1
grep -E '[0-9]{3}' data.log
- 预期输出: (会匹配 SESS001, TXN1001, 500.00, DB500, SESS002, 150 等)
-
查找以 [2025-04-05 开头的行:
1
grep -E '^\[2025-04-05' data.log
- 说明:
[是元字符,需要用\转义。 - 预期输出: 所有行。
- 说明:
-
查找包含 ERROR 或 WARN 的行:
1 2 3 4
grep -E 'ERROR|WARN' data.log 对比: grep -iE '(ERROR|WARN)' data.log grep -iwE '(ERROR|WARN)' data.log
-
预期输出:
1 2 3 4
[2025-04-05 22:31:15] WARN: Disk space low on /dev/sda1. Usage: 95% [2025-04-05 22:33:10] ERROR: Failed to connect to database 'order_db'. Code: DB500 [2025-04-05 22:35:05] ERROR: NullPointerException in PaymentService.java:150 [2025-04-05 22:37:15] WARN: High CPU usage detected. Load: 5.5
-
-
查找包含 Session ID (格式为 SESS 跟随至少一个数字) 的行:
1
grep -E 'Session ID: SESS[0-9]+' data.log
-
预期输出:
1 2
[2025-04-05 22:30:01] INFO: User 'alice' logged in successfully. Session ID: SESS001 [2025-04-05 22:33:12] INFO: User 'bob' logged in successfully. Session ID: SESS002
-
-
查找包含金额 (数字和小数点) 的行:
1
grep -E 'Amount: [0-9]+\.[0-9]+' data.log
-
说明:
\.匹配实际的小数点。 -
预期输出:
1
[2025-04-05 22:32:05] INFO: Transaction processed. ID: TXN1001, Amount: 500.00
-
-
只提取日志中的时间戳 (HH:MM:SS 格式):
1
grep -o -E '[0-9]{2}:[0-9]{2}:[0-9]{2}' data.log
-
预期输出:
1 2 3 4 5 6 7 8 9 10 11
22:30:01 22:31:15 22:32:05 22:33:10 22:33:12 22:34:00 22:35:05 22:36:10 22:37:15 22:38:00 22:38:05
-
思考与挑战: 如何使用 grep 找到包含 IP 地址的行?(提示: IP 地址通常是 数字.数字.数字.数字 的格式,每个数字在 0-255 之间。一个简单的 regex 可以是 [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+,但更精确的匹配会更复杂。)
2.2 文本转换-sed
前置要准备的数据样本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 需要提前创建日志文件
touch sample_log.txt
# 创建/覆盖样本日志文件
echo "[2025-04-04 12:30:01] INFO: User 'hadoop' logged in from 192.168.80.1" > sample_log.txt
echo "[2025-04-04 12:30:05] WARN: Disk space low on /data partition." >> sample_log.txt
echo "[2025-04-04 12:31:10] ERROR: Failed to connect to database [Code: DB500]" >> sample_log.txt
echo "[2025-04-04 12:31:15] INFO: Process started successfully (PID: 12345)" >> sample_log.txt
echo "[2025-04-04 12:32:00] INFO: User 'guest' accessed /home/guest" >> sample_log.txt
echo "[2025-04-04 12:32:05] ERROR: NullPointerException at com.example.Service:120" >> sample_log.txt
echo "[2025-04-04 12:33:01] INFO: User 'hadoop' performed action X" >> sample_log.txt
echo '[2025-04-04 12:33:45] SEVERE: Unexpected system halt!' >> sample_log.txt
echo "[2025-04-04 12:34:00] INFO: Data processed: 1000 records" >> sample_log.txt
echo "Malformed line with no timestamp ERROR" >> sample_log.txt
2.2.1 sed介绍
什么是 sed?
sed是一个流编辑器 (Stream Editor)。它一次处理一行输入(来自文件或管道),对其执行指定的编辑命令,然后输出处理后的行。- 非常适合对文本进行自动化的、基于规则的修改。
基本工作原理 (简化版):
- sed从输入(文件或管道)读取一行文本。
- 将这行文本放入一个称为 “模式空间” 的内存缓冲区。
- 按照脚本中的命令顺序,对模式空间中的文本进行处理(例如:查找并替换、删除等)。
- 默认情况下,处理完成后,sed会将模式空间的内容打印到标准输出。
- 读取下一行输入,重复步骤 2-4,直到所有输入行处理完毕。
1
2
3
4
5
6
7
+---------------+ +-----------------+ +-----------------+ +----------------+
| Input Stream | ---> | Read one line | ---> | Pattern Space | ---> | Apply sed cmds | --+
| (File or Pipe)| | into Pattern Spc| | (Buffer) | | (e.g., s, d, p)| |
+---------------+ +-----------------+ +-----------------+ +----------------+ |
^ |
| v
+------------------------------------------------------------------+-- Print Pattern Space (default) --> Output Stream
sed的核心优势与应用场景:
- 批量文本替换: 在大量文件中查找并替换特定字符串。
- 数据清洗: 删除不需要的行(如空行、注释行、调试信息)、规范数据格式。
- 配置文件修改: 在脚本中自动修改配置文件参数。
- 文本格式转换: 例如,给某些行添加前缀/后缀,合并/拆分行(较复杂)。
- 选择性打印: 结合
-n和p命令,可以只打印文件中符合条件的特定行(类似grep,但更灵活)。
2.2.2 sed基本使用
sed语法:
1
2
3
sed [选项] '脚本命令' [输入文件...]
或者
命令 | sed [选项] '脚本命令'
-
选项 :控制
sed的行为 -
‘脚本命令’ 通常是以下形式:
1
[地址范围]命令[参数]
-
地址范围 (可选):
指定命令作用于哪些行。可以是:
- 单个行号:
1,5 - 行号范围:
1,5(第1到5行) - 正则表达式:
/pattern/(匹配模式的行) - 正则范围:
/start_pattern/,/end_pattern/(从匹配 start 的行到匹配 end 的行) $代表最后一行。- 如果省略地址,命令作用于所有行。
- 单个行号:
-
命令 :要执行的操作,如 :
s(替换),d(删除),p(打印),i(插入),a(追加)。
-
-
输入文件 : 如果省略,从标准输入读取。
sed常用选项:
-
-n: **安静模式 **即只处理指定地址范围的行。默认情况下,sed会打印每一行处理后的结果。使用-n后,只有脚本中明确使用p(print) 命令的行才会被打印。1 2 3 4 5 6 7 8
# 默认情况,打印所有行 (即使没做修改) sed '' sample_log.txt # 使用 -n 后,没有任何输出,因为没有 p 命令 sed -n '' sample_log.txt # 使用 -n 和 p,只打印第 3 行 sed -n '3p' sample_log.txt # 使用 -n 和 p,只打印第 1至3 行 sed -n '1,3p' sample_log.txt
-
-e script: **添加脚本 **。允许多个编辑命令在命令行中指定。例如:sed -e 's/a/b/' -e 's/c/d/' file.txt。1 2 3 4
# 先删除包含 DEBUG 的行,然后将 INFO 替换为 INFORMATION sed -e '/DEBUG/d' -e 's/INFO/INFORMATION/' sample_log.txt # 等效于使用分号分隔 (更常用): sed '/DEBUG/d; s/INFO/INFORMATION/' sample_log.txt
-
-f script-file: 从文件读取脚本。当编辑命令很多或很复杂时,可以将它们写入一个文件,然后用-f指定。1 2 3 4 5
# 创建一个脚本文件 commands.sed echo '/DEBUG/d' > commands.sed echo 's/INFO/INFORMATION/' >> commands.sed # 使用 -f 执行脚本文件 sed -f commands.sed sample_log.txt
-
-r或-E: 使用扩展正则表达式 。与grep -E类似,推荐使用此选项以获得更简洁的正则语法。 -
-i[SUFFIX]: 直接修改文件 。这是一个非常危险的选项,因为它会覆盖原始文件。强烈建议在使用-i时提供一个备份文件的后缀SUFFIX,例如 :-i.bak。这样sed会先将原始文件备份为filename.bak,然后再修改filename。==强烈建议在测试脚本时不要使用 -i,或者总是带上备份后缀!==1 2 3 4
# 查找 sample_log.txt 中的 ERROR 并替换为 FAILED,直接修改文件,并备份原文件为 sample_log.txt.bak sed -i.bak 's/ERROR/FAILED/' sample_log.txt # cat sample_log.txt # 查看修改后的文件 # cat sample_log.txt.bak # 查看备份的原文件
地址范围用于指定sed命令作用于哪些行。如果没有指定地址,命令默认作用于所有行。
-
单行号:
N- 只对第 N 行执行命令。1 2
sed '3d' sample_log.txt # 删除的第3行,不影响源文件 sed '1s/INFO/START/' sample_log.txt # 只在第1行将INFO替换为START,不影响源文件
-
行范围:
N,M- 对从第 N 行到第 M 行(包含 N 和 M)的所有行执行命令。1 2
sed '2,4d' sample_log.txt # 删除第 2 到第 4 行 sed '1,3s/ /_/' sample_log.txt # 在第 1 到 3 行,将第一个空格替换为下划线
-
正则表达式地址:
/pattern/- 对匹配正则表达式pattern的行执行命令。1 2
sed '/WARN/d' sample_log.txt # 删除所有包含 WARN 的行 sed '/^\[.*\]/ s/User/Operator/' sample_log.txt # 只在以时间戳开头的行中将 User 替换为 Operator
-
正则表达式范围:
/pattern1/,/pattern2/- 对从匹配pattern1的第一行开始,到匹配pattern2的第一行结束(包含这两行)的所有行执行命令。1 2 3
# 假设有个文件 data.xml 包含 <config> ... </config> 块 # 删除从 <config> 到 </config> 之间的所有行 sed '/<config>/,/<\/config>/d' data.xml
-
特殊地址 $:代表最后一行。1 2
sed '$d' sample_log.txt # 删除最后一行 sed '1,$s/$/./' sample_log.txt # 在所有行的末尾添加一个点号
注意:$在正则表达式中表示一行的末尾,而在命令中表示最后一行。
-
地址 + !:反转地址匹配,即命令作用于不匹配地址的行。1 2 3 4 5 6 7
测试一:删除一个配置文件中的注释行: sed '/^#/d' hadoop-env.sh 测试二:删除一个配置文件中的注释行及空行: sed '/^#/d; /^$/d' hadoop-env.sh 测试三:删除配置行及空行,即不以#开头的行; sed '/^#/!d' config.file # 只保留注释行 (删除所有非 # 开头的行) sed '/INFO/!s/User/Guest/' log.txt # 在不包含 INFO 的行中将 User 替换为 Guest
2.2.3 sed常用命令
常用命令:
s/pattern/replacement/flags: 替换 (Substitute)。这是sed最常用的命令。d: 删除 (Delete)。删除整个模式空间(当前行)。p: 打印 (Print)。打印模式空间的内容。通常与-n选项一起使用。a\text: 追加 (Append)。在当前行之后追加文本text(text 前的\很重要,表示换行)。i\text: 插入 (Insert)。在当前行之前插入文本text。c\text: 更改 (Change)。用文本text替换当前行。y/source-chars/dest-chars/: 转换 (Translate)。对文件中每单个字符进行一对一替换(类似tr命令)。
实操练习 :
1
2
3
4
## 前置准备:使用之前的data.log文件
ls /root/data.log # 确认文件存在
# 查看文件内容
cat data.log
-
打印第 3 行:
1
sed -n '3p' data.log
-
说明:
-n禁止默认打印,3p只打印第 3 行。 -
预期输出:
1
[2025-04-05 22:32:05] INFO: Transaction processed. ID: TXN1001, Amount: 500.00
-
-
删除第 2 行到第 4 行,不影响数据源:
1
sed '2,4d' data.log
-
说明:
2,4d删除第 2 到 4 行。默认打印剩余行。 -
预期输出: (第 1, 5, 6, … 行)
1 2 3 4 5 6 7 8
[2025-04-05 22:30:01] INFO: User 'alice' logged in successfully. Session ID: SESS001 [2025-04-05 22:33:12] INFO: User 'bob' logged in successfully. Session ID: SESS002 [2025-04-05 22:34:00] DEBUG: Checking cache for key 'user:bob' [2025-04-05 22:35:05] ERROR: NullPointerException in PaymentService.java:150 [2025-04-05 22:36:10] INFO: User 'alice' updated profile. [2025-04-05 22:37:15] WARN: High CPU usage detected. Load: 5.5 [2025-04-05 22:38:00] INFO: Batch job 'report_gen' completed. [2025-04-05 22:38:05] error: Payment failed for user 'charlie'. Reason: Insufficient funds.
-
-
删除包含 “DEBUG” 的行:
1
sed '/DEBUG/d' data.log
- 说明:
/DEBUG/d删除匹配/DEBUG/模式的行。 - 预期输出: (除了包含 DEBUG 的那一行)
- 说明:
-
打印包含 “ERROR” 的行 (类似 grep):
1 2 3
sed -n '/ERROR/p' data.log # 或者使用 ERE sed -n -E '/ERROR/p' data.log
-
说明:
-n配合/pattern/p可以实现类似grep的功能。 -
预期输出:
1 2
[2025-04-05 22:33:10] ERROR: Failed to connect to database 'order_db'. Code: DB500 [2025-04-05 22:35:05] ERROR: NullPointerException in PaymentService.java:150
-
-
在第 1 行前插入 “Log Start”:
1
sed '1i\Log Start' data.log
-
预期输出:
1 2 3
Log Start [2025-04-05 22:30:01] INFO: User 'alice' logged in successfully. Session ID: SESS001 ...
-
-
在最后一行 ($) 后追加 “Log End”:
1
sed '$a\Log End' data.log
-
预期输出:
1 2 3
... [2025-04-05 22:38:05] error: Payment failed for user 'charlie'. Reason: Insufficient funds. Log End
思考:sed -e ‘1i\Log Start’ -e ‘$a\Log End’ data.log的作用?
-
-
将第 5 行更改为 “This line was changed”:
1
sed '5c\This line was changed' data.log
-
打印从包含 “WARN” 的行开始,到包含 “ERROR” 的行结束的范围:
1 2 3 4
初步实现: sed -n '/WARN/,/ERROR/p' data.log 改良实现: sed -n '/WARN/{:a;N;/ERROR/!ba;p}' data.log
-
说明: 打印第一个 WARN 到 第一个 ERROR 之间的所有行 (包括这两行)。
-
预期输出:
1 2 3
[2025-04-05 22:31:15] WARN: Disk space low on /dev/sda1. Usage: 95% [2025-04-05 22:32:05] INFO: Transaction processed. ID: TXN1001, Amount: 500.00 [2025-04-05 22:33:10] ERROR: Failed to connect to database 'order_db'. Code: DB500
-
2.2.4 sed核心武器-替换命令
实操练习 (s 命令):
-
将所有 “INFO” 替换为 “Information”:
1
sed -E 's/INFO/Information/g' data.log
- 说明:
-E启用 ERE (虽然这里没用到复杂正则,但养成习惯)。g确保一行中若有多个 “INFO” 都被替换。 - 预期输出: (所有 INFO 变为 Information)
- 说明:
-
将第一个 “User” 替换为 “Client”:
1
sed -E 's/User/Client/' data.log
-
说明: 没有
g,只替换每行第一个匹配项。 -
预期输出: (只有第一个 User 变为 Client)
1 2 3 4 5
[2025-04-05 22:30:01] INFO: Client 'alice' logged in successfully. Session ID: SESS001 ... [2025-04-05 22:33:12] INFO: Client 'bob' logged in successfully. Session ID: SESS002 ... [2025-04-05 22:38:05] error: Payment failed for user 'charlie'. Reason: Insufficient funds.
-
-
将所有小写的 “error” 替换为 “[error]” (使用 &):
1
sed -E 's/error/[&]/g' data.log
-
说明:
&代表匹配到的 “error”。 -
预期输出:
1 2
... [2025-04-05 22:38:05] [error]: Payment failed for user 'charlie'. Reason: Insufficient funds.
-
-
将用户名 (单引号之间的字母) 加上域后缀 “@example.com”:
1 2
# 正则: '([a-z]+)' 匹配并捕获单引号内的连续小写字母 sed -E "s/'([a-z]+)'/'\1@example.com'/g" data.log
-
说明:
- 使用双引号
"包裹sed脚本,因为replacement中也用了单引号。或者用s/'([a-z]+)'/'\''\1@example.com'\''/g也可以。 '([a-z]+)': 匹配',然后([a-z]+)捕获一个或多个小写字母作为第一个分组 (\1),最后匹配'。'\1@example.com': 替换为',然后是捕获到的用户名 (\1),然后是@example.com,最后是'。
- 使用双引号
-
预期输出:
1 2 3 4 5 6 7
[2025-04-05 22:30:01] INFO: User 'alice@example.com' logged in successfully. Session ID: SESS001 ... [2025-04-05 22:33:12] INFO: User 'bob@example.com' logged in successfully. Session ID: SESS002 [2025-04-05 22:34:00] DEBUG: Checking cache for key 'user:bob@example.com' ... [2025-04-05 22:36:10] INFO: User 'alice@example.com' updated profile. [2025-04-05 22:38:05] error: Payment failed for user 'charlie@example.com'. Reason: Insufficient funds.
-
-
只在包含 “WARN” 的行中,将 “Usage: ([0-9]+)%” 替换为 “Usage: \1 percent”:
1
sed -E '/WARN/ s/Usage: ([0-9]+)%/Usage: \1 percent/' data.log
-
说明:
/WARN/是地址,只对匹配 “WARN” 的行执行后面的s命令。([0-9]+)捕获百分比数字。 -
预期输出:
1 2 3 4 5
... [2025-04-05 22:31:15] WARN: Disk space low on /dev/sda1. Usage: 95 percent ... [2025-04-05 22:37:15] WARN: High CPU usage detected. Load: 5.5 ...
-
-
将所有数字替换为 “NUMBER” (演示 g 的效果):
1
sed -E 's/[0-9]+/NUMBER/g' data.log
- 预期输出: (所有连续的数字都被替换)
-
将日期格式 YYYY-MM-DD 替换为 MM/DD/YYYY:
1 2
# 正则: ([0-9]{4})-([0-9]{2})-([0-9]{2}) 捕获年、月、日 sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\2\/\3\/\1/g' data.log
-
说明:
\1是年,\2是月,\3是日。替换时调整顺序并用/分隔。注意/需要转义\/因为它也是s命令的分隔符。 -
或者使用不同分隔符:
1
sed -E 's#([0-9]{4})-([0-9]{2})-([0-9]{2})#\2/\3/\1#g' data.log -
预期输出: (日期格式改变)
1 2 3
[04/05/2025 22:30:01] INFO: User 'alice' logged in successfully. Session ID: SESS001 [04/05/2025 22:31:15] WARN: Disk space low on /dev/sda1. Usage: 95% ...
-
2.3 文本分析-awk
2.3.1 awk介绍
awk (以其三位作者 Aho, Weinberger, Kernighan 的姓氏首字母命名) 是一个强大的文本 分析 工具。与 grep (行过滤) 和 sed (行编辑) 不同,awk 的核心优势在于它能将输入的每一行分解 成多个 字段 (列),然后对这些字段进行处理、计算、比较和格式化输出。
awk 与 grep, sed 的关系:
grep: 擅长 查找 包含特定模式的 行。输出通常是整行。sed: 擅长 编辑 行 的内容,进行替换、删除、插入等操作。awk: 擅长 处理 行 中的 字段,进行计算、逻辑判断、格式化输出,生成报告。
这三者经常在 Shell 管道中 协同工作:grep 筛选出相关行,sed 清洗和格式化这些行,awk 对格式化后的数据进行字段级分析和报告生成。
基本工作原理:记录与字段
awk 逐行读取输入(默认情况下,每行是一个记录 Record),并将每行按照指定的分隔符(默认为空白字符,如空格或 Tab)分割成多个字段 (列)。然后,你可以对这些字段进行处理。
1
2
3
4
5
6
7
Input Line (Record): "2025-04-04 10:07:30 ERROR: Failed connection"
| | | | | |
+-----+-------+-----+------+--------+--------+
Field Separator (FS): (whitespace by default)
| | | | | |
Fields: $1 $2 $3 $4 $5 $6 ... ($NF)
Record Separator (RS): \n (newline by default)
2.3.2 awk语法格式
命令行格式:
1
2
3
awk [OPTIONS] 'program' [FILE...]
# 或者通过管道
command | awk [OPTIONS] 'program'
OPTIONS: 控制awk行为的选项,最常用的是-F。-F fs: 指定输入字段分隔符。fs可以是单个字符 (如-F':',-F',') 或正则表达式。- 在 AWK 中,如果不使用
-F指定分隔符,默认的字段分隔符是 空格和制表符(" \t")的组合,并且会自动压缩连续的空白字符(即多个空格或制表符会被视为一个分隔符)。
program: 由一个或多个'pattern { action }'语句组成。整个program通常用单引号' '包裹。FILE...: 可选的输入文件名。省略时从标准输入读取。
awk中的``program 结构:‘pattern { action }’`
-
pattern: 一个条件,用于决定action是否对当前记录执行。可以是正则表达式、比较表达式、BEGIN、END等。如果省略pattern,则action对所有行执行。1 2 3 4 5
awk -F',' '{程序内容}' 文件 awk -F',' 'BEGIN{程序内容}' 文件 awk -F',' 'END{程序内容}' 文件 awk -F',' '/正则表达式/{程序内容}' 文件 awk -F',' '数值1>数值2{程序内容}' 文件
-
action: 一组用花括号{}包裹的命令语句,在pattern匹配时执行。如果省略action,则默认动作为print $0(打印整行)。 -
可以在一个
program中包含多个pattern { action }语句,awk会对每一行依次检查所有pattern。
一个典型的 awk 程序结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BEGIN {
# 初始化代码块:在处理任何输入行之前执行一次。
# 例如:设置变量、打印表头。
}
pattern1 {
# 操作代码块1:当输入行匹配 pattern1 时执行。
# pattern 可以是正则表达式、条件表达式等。
# 如果省略 pattern,则对所有行执行操作。
}
pattern2 {
# 操作代码块2:当输入行匹配 pattern2 时执行。
}
... # 可以有多个 pattern { action } 块
END {
# 结束代码块:在处理完所有输入行之后执行一次。
# 例如:打印总计、最终报告。
}
BEGIN和END块是可选的。pattern { action }块也是可选的,但至少要有一个 action(即使 pattern 省略)。action部分由一系列awk语句组成,用分号;或换行分隔。
2.3.4 awk常用内置变量
awk 提供了许多有用的内置变量,用于访问记录信息和控制行为。
$0: 当前处理的整个记录(行)内容。$1, $2, ... $N: 第 1, 2, …, N 个字段的内容。$NF: 最后一个字段的内容。NF: 当前记录的字段数量。NR: 当前处理的总记录编号(行号),从 1 开始计数 。FNR: 当前文件中的记录数 (处理多文件时有用)。FS: 输入字段分隔符。默认为空格或 Tab。可通过-F或BEGIN { FS="..." }设置。OFS: 输出字段分隔符 。默认为一个空格。print命令在输出多个字段(用逗号分隔)时会使用OFS。可通过BEGIN { OFS="..." }设置。RS: 输入记录分隔符 。默认为换行符\n。ORS: 输出记录分隔符 。默认为换行符\n。print命令执行后会自动添加ORS。FILENAME: 当前正在处理的文件名。
示例:
1
2
3
4
5
6
7
8
# 打印行号、字段数、第一个字段、最后一个字段
awk '{ print "Line:", NR, "Fields:", NF, "First:", $1, "Last:", $NF }' data.txt
# 使用 /etc/passwd,以 : 分隔,输出 用户名,UID,Shell (用逗号分隔输出)
awk -F':' 'BEGIN { OFS="," } { print $1, $3, $7 }' /etc/passwd
# 处理多个文件时,打印文件名和行号
awk '{ print FILENAME, NR, FNR, $0 }' data.txt data.txt
2.3.5 awk示例
准备工作:
- 创建样本数据文件:
sample_log.txt - 使用
/etc/passwd文件
sample_log.txt文件数据样本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 需要提前创建日志文件
# touch sample_log.txt
# 创建/覆盖样本日志文件
echo "[2025-04-04 12:30:01] INFO: User 'hadoop' logged in from 192.168.80.1" > sample_log.txt
echo "[2025-04-04 12:30:05] WARN: Disk space low on /data partition." >> sample_log.txt
echo "[2025-04-04 12:31:10] ERROR: Failed to connect to database [Code: DB500]" >> sample_log.txt
echo "[2025-04-04 12:31:15] INFO: Process started successfully (PID: 12345)" >> sample_log.txt
echo "[2025-04-04 12:32:00] INFO: User 'guest' accessed /home/guest" >> sample_log.txt
echo "[2025-04-04 12:32:05] ERROR: NullPointerException at com.example.Service:120" >> sample_log.txt
echo "[2025-04-04 12:33:01] INFO: User 'hadoop' performed action X" >> sample_log.txt
echo "[2025-04-04 12:33:45] SEVERE: Unexpected system halt" >> sample_log.txt
echo "[2025-04-04 12:34:00] INFO: Data processed: 1000 records" >> sample_log.txt
echo "Malformed line with no timestamp ERROR" >> sample_log.txt
练习任务:
-
打印 sample_log.txt 中每行的行号 (NR) 和第二个字段 ($2)
1 2 3
awk '{ print NR, $2 }' sample_log.txt awk 'BEGIN{print "行号"," ","第2列"}{ print NR," ", $2 }' sample_log.txt
-
从 sample_log.txt 中提取所有登录用户的用户名 (假设格式为 User ‘username’ …)
1 2 3
# [2025-04-04 12:30:01] INFO: User 'hadoop' logged in from 192.168.80.1 awk -F"'" '/User .* logged in/ { print $2 }' sample_log.txt
-
打印 /etc/passwd 文件中 UID ($3) 小于 10 的用户名 ($1)
1
awk -F':' '$3 < 10 { print $1 }' /etc/passwd
-
打印 /etc/passwd 文件中第 5 到第 10 行的用户名和 Shell,用 Tab 分隔
1
awk -F':' 'BEGIN { OFS="\t" } NR>=5 && NR<=10 { print $1, $7 }' /etc/passwd
-
计算 ls -l 输出中所有普通文件 (^-) 的总大小 (字节,字段 $5)
1
ls -l | awk '/^-/ { total_size += $5 } END { print "Total file size:", total_size, "bytes" }'
-
格式化输出 /etc/passwd 中的用户名和家目录,宽度对齐
注意:如果要对输出的数据进行格式化操作,那么必须使用printf函数,如果使用print会使格式字符失效
1
awk -F':' 'BEGIN { printf "%-20s %-s\n", "USERNAME", "HOME DIRECTORY" } { printf "%-20s %-s\n", $1, $6 }' /etc/passwd
-
统计 sample_log.txt 中 “ERROR” 和 “WARN” 日志的数量
1
awk '/ERROR/ { error_count++ } /WARN/ { warn_count++ } END { printf "Errors: %d\nWarnings: %d\n", error_count, warn_count }' sample_log.txt
1
2
3
4
awk
printf %-s 表示字符串 printf "%-s", "字符串"
printf %d 表示整数 printf "%d" , 100
printf %f 表示小数
Linux中使用的命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
##目录和文件
cd 目录
mkdir -p 目录
touch /目录/文件
rm -rf 目录或文件
cp 文件 /目录/文件2
cp -r 目录 /目录/
mv 旧名字 新名字
mv 文件 /移动到的目标
pwd
ll
find /目录 -name 'h*.log'
cat /目录/小文件
less /目录/大文件
tail -f /目录/文件
vim 文件名
echo "文本内容" > /目录/文件
echo "追加的文本内容" >> 文件
ps -ef | grep mysql
kill -9 进程id
命令1 && 命令
正则表达式: 使用特殊符号和普通字符组成的一个规则模式。 利用这个规则模式,可以实现从文本数据中匹配到符合要求的数据。
grep -i 'error' 文件