Linux文本处理三剑客--grep, sed ,awk

Linux文本处理核心技术:正则表达式与grep、sed、awk工具实战指南

Posted by Wenqin on September 22, 2025

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

实操练习:

  1. 查找包含 “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
      
  2. 查找包含 “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.
      
  3. 查找不包含 “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.
      
  4. 查找包含 “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
      
  5. 统计包含 “WARN” 的行数:

    1
    
    grep -c 'WARN' data.log
    
    • 预期输出:

      1
      
      2
      
  6. 查找包含单词 “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.
      
  7. 列出当前目录下哪些 .log 文件包含 “fail”:

    1
    
    grep -l 'failed' *.log
    
    • 预期输出:

      1
      
      data.log
      
  8. 查找包含 “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.
      
  9. 从 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):

  1. 查找包含 3 位数字的行:

    1
    
    grep -E '[0-9]{3}' data.log
    
    • 预期输出: (会匹配 SESS001, TXN1001, 500.00, DB500, SESS002, 150 等)
  2. 查找以 [2025-04-05 开头的行:

    1
    
    grep -E '^\[2025-04-05' data.log
    
    • 说明: [ 是元字符,需要用 \ 转义。
    • 预期输出: 所有行。
  3. 查找包含 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
      
  4. 查找包含 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
      
  5. 查找包含金额 (数字和小数点) 的行:

    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
      
  6. 只提取日志中的时间戳 (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)。它一次处理一行输入(来自文件或管道),对其执行指定的编辑命令,然后输出处理后的行。
  • 非常适合对文本进行自动化的、基于规则的修改。

基本工作原理 (简化版):

  1. sed从输入(文件或管道)读取一行文本。
  2. 将这行文本放入一个称为 “模式空间” 的内存缓冲区。
  3. 按照脚本中的命令顺序,对模式空间中的文本进行处理(例如:查找并替换、删除等)。
  4. 默认情况下,处理完成后,sed会将模式空间的内容打印到标准输出。
  5. 读取下一行输入,重复步骤 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的核心优势与应用场景:

  • 批量文本替换: 在大量文件中查找并替换特定字符串。
  • 数据清洗: 删除不需要的行(如空行、注释行、调试信息)、规范数据格式。
  • 配置文件修改: 在脚本中自动修改配置文件参数。
  • 文本格式转换: 例如,给某些行添加前缀/后缀,合并/拆分行(较复杂)。
  • 选择性打印: 结合 -np 命令,可以只打印文件中符合条件的特定行(类似 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
  1. 打印第 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. 删除第 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.
      
  3. 删除包含 “DEBUG” 的行:

    1
    
    sed '/DEBUG/d' data.log
    
    • 说明: /DEBUG/d 删除匹配 /DEBUG/ 模式的行。
    • 预期输出: (除了包含 DEBUG 的那一行)
  4. 打印包含 “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
      
  5. 在第 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
      ... 
      
  6. 在最后一行 ($) 后追加 “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的作用?

  7. 将第 5 行更改为 “This line was changed”:

    1
    
    sed '5c\This line was changed' data.log
    
  8. 打印从包含 “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 命令):

  1. 将所有 “INFO” 替换为 “Information”:

    1
    
    sed -E 's/INFO/Information/g' data.log
    
    • 说明: -E 启用 ERE (虽然这里没用到复杂正则,但养成习惯)。g 确保一行中若有多个 “INFO” 都被替换。
    • 预期输出: (所有 INFO 变为 Information)
  2. 将第一个 “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.
      
  3. 将所有小写的 “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.
      
  4. 将用户名 (单引号之间的字母) 加上域后缀 “@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.
      
  5. 只在包含 “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
      ...
      
  6. 将所有数字替换为 “NUMBER” (演示 g 的效果):

    1
    
    sed -E 's/[0-9]+/NUMBER/g' data.log
    
    • 预期输出: (所有连续的数字都被替换)
  7. 将日期格式 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 的核心优势在于它能将输入的每一行分解 成多个 字段 (列),然后对这些字段进行处理、计算、比较和格式化输出。

awkgrep, 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 是否对当前记录执行。可以是正则表达式、比较表达式、BEGINEND 等。如果省略 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 {
  # 结束代码块:在处理完所有输入行之后执行一次。
  # 例如:打印总计、最终报告。
}
  • BEGINEND 块是可选的。
  • 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。可通过 -FBEGIN { FS="..." } 设置。
  • OFS: 输出字段分隔符 。默认为一个空格。print 命令在输出多个字段(用逗号分隔)时会使用 OFS。可通过 BEGIN { OFS="..." } 设置。
  • RS: 输入记录分隔符 。默认为换行符 \n
  • ORS: 输出记录分隔符 。默认为换行符 \nprint 命令执行后会自动添加 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 

练习任务:

  1. 打印 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
    
  2. 从 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
    
  3. 打印 /etc/passwd 文件中 UID ($3) 小于 10 的用户名 ($1)

    1
    
    awk -F':' '$3 < 10 { print $1 }' /etc/passwd
    
  4. 打印 /etc/passwd 文件中第 5 到第 10 行的用户名和 Shell,用 Tab 分隔

    1
    
    awk -F':' 'BEGIN { OFS="\t" } NR>=5 && NR<=10 { print $1, $7 }' /etc/passwd
    
  5. 计算 ls -l 输出中所有普通文件 (^-) 的总大小 (字节,字段 $5)

    1
    
    ls -l | awk '/^-/ { total_size += $5 } END { print "Total file size:", total_size, "bytes" }'
    
  6. 格式化输出 /etc/passwd 中的用户名和家目录,宽度对齐

    注意:如果要对输出的数据进行格式化操作,那么必须使用printf函数,如果使用print会使格式字符失效

    1
    
    awk -F':' 'BEGIN { printf "%-20s %-s\n", "USERNAME", "HOME DIRECTORY" } { printf "%-20s %-s\n", $1, $6 }' /etc/passwd
    
  7. 统计 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' 文件

相关文档

  1. Linux系统安装
  2. Linux命令
  3. Shell命令
  4. Shell综合应用实战
  5. Linux文本处理