三剑客之sed

sed(Stream Editor,流编辑器)是 Linux 运维文本处理的核心工具,用于逐行读取文本、按规则处理、输出结果,无需打开文件即可完成替换、删除、新增、提取等操作。它适合处理配置文件、日志分析、批量修改文本内容。

一、sed核心语法与工作模式

1、基础语法

sed [选项] '处理指令' 文件名
# 或从管道读取输入
cat 文件名 | sed [选项] '处理指令'

2、工作模式(核心)

sed为单位处理文本,流程如下:

  1. 从输入中读取一行文本到内存(称为模式空间
  2. 处理指令 处理模式空间中的内容
  3. 输出处理后的内容到标准输出
  4. 重复上述步骤,直到所有行处理完毕

3、必背核心选项

选项 作用 运维常用示例
-n 静默模式,只输出被处理的行(默认输出所有行) sed -n '2p' test.txt(只打印第 2 行)
-i 直接修改原文件(危险!建议先备份) sed -i.bak 's/old/new/g' my.cnf(备份后替换)
-e 执行多个处理指令 sed -e '1d' -e 's/old/new/g' test.txt
-f 从脚本文件读取处理指令 sed -f script.sed test.txt
-r 启用扩展正则表达式(无需转义 ()+{} sed -r 's/(old1 old2)/new/g' test.txt
-s 将多个文件视为独立文件处理(默认视为一个整体) sed -s 's/^/prefix:/' *.txt

4、处理指令格式

处理指令由 地址范围 + 操作命令 组成,格式:

[地址范围]操作命令[参数]

地址范围:指定要处理的行(可选,默认处理所有行)

  • 单行:5(第 5 行)、$(最后一行)
  • 范围:2,5(第 2-5 行)、1,+3(第 1 行及后续 3 行)
  • 正则匹配:/error/(匹配包含 error 的行)

操作命令:核心功能(如 p 打印、d 删除、s 替换)



二、sed核心操作命令(高频)

1、打印行:p(配合 -n 使用)

作用:打印模式空间中的内容,默认会重复输出,需加 -n 静默模式。

# 打印第 3 行
sed -n '3p' /etc/passwd

# 打印第 2-5 行
sed -n '2,5p' /etc/passwd

# 打印包含 "root" 的行(正则匹配)
sed -n '/root/p' /etc/passwd

# 打印从第1行到第一个包含 "mysql" 的行
sed -n '1,/mysql/p' /etc/passwd

# 打印最后一行
sed -n '$p' test.txt

2、删除行:d

作用:删除模式空间中的行,不影响原文件(除非加 -i)。

# 删除第 1 行
sed '1d' test.txt

# 删除第 2-5 行
sed '2,5d' test.txt

# 删除包含 "INFO" 的行(过滤日志)
sed '/INFO/d' xenon.log

# 删除空行
sed '/^$/d' my.cnf

# 删除注释行(以 # 开头)
sed '/^#/d' /etc/mysql/my.cnf

# 删除最后一行
sed '$d' test.txt

3、替换内容:s(最常用)

作用:替换文本内容,核心命令,格式:

sed 's/旧字符串/新字符串/替换标记' 文件名
替换标记 作用
g 全局替换(一行中所有匹配项,默认只替换第一个)
i 忽略大小写
p 打印替换后的行(配合 -n
w 将替换后的行写入新文件

基础替换示例

# 将每行第一个 "old" 替换为 "new"
sed 's/old/new/' test.txt

# 全局替换(所有 "old" 替换为 "new")
sed 's/old/new/g' test.txt

# 忽略大小写替换
sed 's/error/ERROR/gi' app.log

# 替换并只输出替换后的行
sed -n 's/old/new/gp' test.txt

# 替换并写入新文件
sed 's/old/new/gw output.txt' test.txt

# 直接修改原文件(备份为 my.cnf.bak)
sed -i.bak 's/gtid_mode=OFF/gtid_mode=ON/g' /etc/mysql/my.cnf

正则替换示例(需 -r

# 将 192.168.1.x 替换为 10.0.0.x
sed -r 's/(192\.168\.1)\.([0-9]+)/10.0.0.\2/g' test.txt

# 批量注释配置行(在包含 "log_bin" 的行前加 #)
sed -r 's/(.*log_bin.*)/#\1/' my.cnf

# 批量取消注释(去掉 # 开头的行的注释)
sed -r 's/^#(.*)/\1/' my.cnf

使用反向引用

  • \1:引用第 1 个分组的内容(即 192.168.1)。

  • .:插入一个字面量点号。

  • \2:引用第 2 个分组的内容(即 IP 的最后一段数字)

  • .*:匹配任意字符(除换行符)0 次或多次。

    • 第一个 .* 匹配 log_bin 前面的所有内容(如空格、制表符等)。
    • 第二个 .* 匹配 log_bin 后面的所有内容(如具体的路径或配置值)。

4、新增行:a(追加)、i(插入)

命令 作用 示例
a 在指定行追加内容 sed '2a new line' test.txt(第 2 行后加新行)
i 在指定行插入内容 sed '2i new line' test.txt(第 2 行前加新行)
# 在第 3 行后追加一行配置
sed '3a gtid_mode = ON' my.cnf

# 在包含 "mysql" 的行前插入注释
sed '/mysql/i # 这是 MySQL 配置' my.cnf

# 在最后一行后追加内容
sed '$a # End of file' test.txt

5、替换整行:c

作用:将指定行替换为新内容。

# 将第 2 行替换为新内容
sed '2c new line' test.txt

# 将包含 "old" 的行替换为新内容
sed '/old/c new content' test.txt

6、 读取文件内容:r

作用:将指定文件的内容插入到目标行后。

# 在第 5 行后插入 backup.txt 的内容
sed '5r backup.txt' test.txt

# 在包含 "mysql" 的行后插入配置内容
sed '/mysql/r extra.cnf' my.cnf


三、场景实战

场景 1:批量修改 MySQL 配置文件

# 1. 启用 GTID 模式(直接修改原文件,备份 .bak)
sed -i.bak -r 's/(gtid_mode=).*/\1ON/; s/(enforce_gtid_consistency=).*/\1ON/' /etc/my.cnf

# 2. 注释掉所有 log_bin 相关配置
sed -i 's/^log_bin/#log_bin/' /etc/my.cnf

# 3. 在 [mysqld] 段后添加半同步复制配置
sed '/\[mysqld\]/a rpl_semi_sync_master_enabled = 1\nrpl_semi_sync_master_wait_for_slave_count = 1' /etc/my.cnf

场景 2:批量修改 K8s YAML 文件(替换镜像版本)

# 将所有 Xenon 镜像版本从 v3.0.0 改为 v3.0.1
sed -i 's/radondb\/xenon:v3.0.0/radondb\/xenon:v3.0.1/g' mysql-cluster.yaml

# 给所有 Pod 添加资源限制(在 spec.template.spec 后插入)
sed '/spec.template.spec/a \ \ \ \ resources:\n\ \ \ \ \ limits:\n\ \ \ \ \ \ cpu: 2\n\ \ \ \ \ \ memory: 4Gi' mysql-cluster.yaml

场景 3:提取日志中的关键信息(结合正则)

# 从 Nginx 日志中提取所有访问 IP 和 URL
sed -n 's/^\([0-9.]\+\).*"\([A-Z]\+ \/.*\) HTTP.*/\1 \2/p' access.log


# 从 MySQL 慢查询日志中提取执行时间超过 10s 的 SQL
sed -n '/Query_time: [0-9]\{2,\}/p' slow.log

注意:如果加上-r扩展正则,就可以不用转义()和+

sed -rn 's/^([0-9.]+).*"([A-Z]+ \/.*) HTTP.*/\1 \2/p' access.log

^\([0-9.]\+\).*"\([A-Z]\+ \/.*\) HTTP.*

这里定义了两个捕获分组,用于提取我们想要的数据:

  • 第一部分(提取 IP):^\([0-9.]\+\)
    • ^:匹配行首。
    • [0-9.]:匹配数字或点号。
    • \+:匹配前面的字符 1 次或多次。
    • 含义:匹配行首的 IP 地址(如 192.168.1.1),并将其存入 分组 1\1)。
  • 中间部分:.\*"
    • .:匹配任意字符。
    • *:匹配 0 次或多次。
    • ":匹配双引号。
    • 含义:跳过 IP 地址和请求行之间的所有内容(通常是时间戳、时区等),直到遇到第一个双引号 "
  • 第二部分(提取请求):\([A-Z]\+ \/.\*\)
    • [A-Z]\+:匹配一个或多个大写字母(即 HTTP 方法,如 GETPOST)。
    • \/:匹配一个空格和一个斜杠 /(URL 的开头)。
    • .*:匹配后面的任意字符(即具体的路径,如 index.html)。
    • 含义:匹配整个请求部分(如 GET /index.html),并将其存入 分组 2\2)。
  • 结尾部分: HTTP.\*
    • 匹配后面的 HTTP/1.1 等协议版本信息,确保我们只截取到路径为止。

5. 替换内容:\1 \2

  • \1:引用分组 1(IP 地址)。
  • :中间加一个空格。
  • \2:引用分组 2(请求方法 + 路径)。

🎯 实际效果演示

假设你的 access.log 原始内容如下(标准 Nginx/Apache 格式):

192.168.1.100 - - [10/Oct/2023:13:55:36 +0800] "GET /index.html HTTP/1.1" 200 1234
10.0.0.5 - - [10/Oct/2023:13:56:01 +0800] "POST /api/login HTTP/1.1" 404 567
Invalid log line without proper format

运行命令后,输出结果为:

192.168.1.100 GET /index.html
10.0.0.5 POST /api/login

注意:第三行 Invalid... 因为不符合正则规则(没有 IP 开头或没有 HTTP 请求格式),所以被 -np 过滤掉了,没有输出。



场景 4:批量重命名文件(配合 find

# 将所有 .txt 文件后缀改为 .bak
find . -name "*.txt" | sed 'p; s/\.txt/\.bak/' | xargs -n2 mv
# 解释:
# p → 打印原文件名;s → 替换为新文件名;xargs -n2 mv → 执行 mv 原文件 新文件

从当前目录(.)开始,递归查找所有名字以 .txt 结尾的文件。

./a.txt
./b.txt
./sub/c.txt

| sed 'p; s/\.txt/\.bak/'
这是整个命令最精彩的部分。sed 这里做了两件事:

  • p:打印当前行(即原文件名)。
  • ;:分号,分隔两个命令。
  • s/\.txt/\.bak/:将当前行中的 .txt 替换为 .bak
  • 结果:对于每一个输入的文件名,sed 都会输出两行——第一行是原名,第二行是新名。
  • 输出示例
./a.txt       <-- 原样打印 (p)
./a.bak       <-- 替换后打印 (s/...)
./b.txt       <-- 原样打印
./b.bak       <-- 替换后打印

| xargs -n2 mv

  • xargs:将标准输入转换为参数传递给后面的命令。
  • -n2:这是关键参数,意思是每次只取 2 个参数(即两行文本)传递给 mv 命令。
  • mv:移动/重命名命令。
  • 执行逻辑:
    1. xargs 读取前两行:./a.txt./a.bak
    2. 执行命令:mv ./a.txt ./a.bak(把 a.txt 改名为 a.bak)。
    3. xargs 读取接下来的两行:./b.txt./b.bak
    4. 执行命令:mv ./b.txt ./b.bak
posted @ 2026-04-17 02:01  mangolxh  阅读(98)  评论(0)    收藏  举报