sed 文本处理工具完整指南

sed 学习笔记 -- 流编辑器完全掌握

分类:基础知识

核心主题:sed 流编辑器完整学习笔记

主要内容:全面系统地讲解 sed 流编辑器,涵盖安装配置、基本操作(增删改查)、正则表达式、替换操作、地址范围、高级命令、多行模式处理及与 Claude Code 结合使用。

关键词:sed, 流编辑器, 文本处理, Linux命令, 正则表达式, 字符串替换, 文件编辑, 文本转换, Claude Code

目录

  1. sed 概述与基本概念
  2. 安装与基本使用
  3. 基本操作(增删改查)
  4. 正则表达式与模式匹配
  5. 替换操作详解
  6. 地址范围与行选择
  7. 高级命令(分支、测试、标签)
  8. 多行模式处理
  9. 脚本化与实用技巧
  10. 与 Claude Code 结合使用
  11. 常见问题与排错
  12. 核心总结

一、sed 概述与基本概念

1.1 什么是 sed

sed(Stream Editor,流编辑器)是一个强大的非交互式文本处理工具,由贝尔实验室的 Lee E. McMahon 在 1973 年至 1974 年间开发。它从输入流(文件或管道)中读取文本,按照用户指定的编辑命令对文本进行逐行处理,并将结果输出到标准输出。

sed 的核心设计思想

sed 的设计遵循 Unix 哲学——"做一件事,并把它做好"。它专门用于对文本流执行编辑操作,不需要打开交互式编辑器,也不消耗大量内存。sed 一次只处理一行文本,将其读入模式空间(pattern space),执行编辑命令,然后输出结果,接着读取下一行继续处理。

1.2 sed 的工作原理

sed 的工作流程可以概括为三个循环步骤:

读取一行 模式空间 执行命令 输出结果 读取下一行

具体来说:

模式空间 vs 保持空间:

sed 有两个内部缓冲区:模式空间(pattern space)是主工作区,每次处理一行;保持空间(hold space)是一个辅助缓冲区,可以暂存数据,用于跨行操作。初学者先掌握模式空间即可,保持空间在多行处理中发挥作用。

1.3 sed 与 awk、grep 的对比

工具定位输入输出典型用途
grep文本搜索器文件/标准输入匹配的行搜索过滤文本
sed流编辑器文件/标准输入编辑后的文本文本替换、删除、插入
awk文本分析器结构化文本格式化的报告字段提取、数据统计

何时选择 sed?

  • 需要在脚本中执行简单的文本替换(如配置文件修改)
  • 需要批量删除或插入特定行
  • 需要在不打开编辑器的情况下处理大文件
  • 管道流水线中的一个环节(如过滤和转换输出)

二、安装与基本使用

2.1 sed 的版本与安装

sed 有多个实现版本,最常用的是 GNU sed(Linux 系统默认)和 BSD sed(macOS 系统默认)。两者在功能上基本一致,但部分选项和正则表达式的行为存在细微差异。

操作系统默认 sed检查命令安装/更新
Linux (Ubuntu/Debian)GNU sedsed --versionapt install sed
Linux (CentOS/RHEL)GNU sedsed --versionyum install sed
macOSBSD sedsed --version(可能报错)brew install gnu-sed
Windows (WSL)GNU sedsed --version随 WSL 发行版预装
Windows (Git Bash)GNU sedsed --version随 Git for Windows 安装

GNU sed 与 BSD sed 的重要区别

  • -i 选项:GNU sed 的 sed -i 's/old/new/g' file 可以直接修改文件;BSD sed 需要 sed -i '' 's/old/new/g' file-i 后必须跟一个备份扩展名参数,空字符串表示不备份)。
  • -E 选项:GNU sed 支持 -r(兼容)和 -E(推荐)启用扩展正则;BSD sed 只支持 -E
  • \c 转义:GNU sed 支持 \c 等更多转义序列。

2.2 基本语法

sed 的基本命令格式如下:

# 基本语法 sed [选项] '命令' 文件名 sed [选项] '地址范围命令' 文件名 sed [选项] -f 脚本文件 文件名 # 常用选项 -n # 关闭自动打印(仅打印被 p 命令指定的行) -e 命令 # 指定多个编辑命令 -f 脚本文件 # 从文件中读取 sed 命令 -i[后缀] # 原地修改文件(可选指定备份后缀) -E # 使用扩展正则表达式 -r # 同 -E(GNU sed 兼容选项)

2.3 第一个 sed 命令

# 准备测试文件 echo -e "hello world\nnice to meet you\nhello sed" > test.txt cat test.txt # 最简单的 sed 命令:打印所有行(相当于 cat) sed '' test.txt # 打印匹配 "hello" 的行 sed -n '/hello/p' test.txt # 输出: # hello world # hello sed # 将 "hello" 替换为 "hi" sed 's/hello/hi/' test.txt # 输出: # hi world # nice to meet you # hi sed # 删除包含 "nice" 的行 sed '/nice/d' test.txt # 输出: # hello world # hello sed

2.4 命令行中使用单引号与双引号

# 单引号:所有字符按字面处理,变量不会被展开 sed 's/$HOME/\/home/' file.txt # $HOME 作为字面字符串 # 双引号:Shell 变量会被展开 home="/home/user" sed "s|OLD_HOME|$home|" file.txt # $home 被替换为变量值 # 使用双引号时需要注意转义 sed "s/\\$HOME/\/home/" file.txt # \\$ 对 $ 进行转义 # 推荐:在 sed 命令中使用单引号,变量用引号拼接 prefix="user" sed 's/^/'$prefix'/' file.txt # 关闭单引号插入变量 sed 's/^/'"$prefix"'/' file.txt # 更安全的写法

引号使用建议

始终优先使用单引号包裹 sed 命令。如果需要在 sed 命令中嵌入 Shell 变量,先关闭单引号,用双引号包裹变量,再重新打开单引号。这样可以避免 Shell 对 sed 命令中的特殊字符进行不必要的解释。

三、基本操作(增删改查)

3.1 打印(p 命令)

p 命令用于打印模式空间的内容。通常与 -n 选项一起使用,避免每一行都被打印两次。

# 打印第 2 行 sed -n '2p' file.txt # 打印第 2 到第 5 行 sed -n '2,5p' file.txt # 打印匹配模式的行 sed -n '/error/p' log.txt # 打印第 2 行及其之后的 3 行 sed -n '2,+3p' file.txt # 打印奇数行 sed -n '1~2p' file.txt # 打印偶数行 sed -n '2~2p' file.txt # 打印最后一行 sed -n '$p' file.txt # 打印从第 3 行到最后一行的内容 sed -n '3,$p' file.txt

3.2 删除(d 命令)

d 命令删除模式空间的内容,并立即读取下一行开始新的循环(不会执行剩余的 sed 命令)。

# 删除第 2 行 sed '2d' file.txt # 删除第 2 到第 5 行 sed '2,5d' file.txt # 删除空白行 sed '/^$/d' file.txt # 删除包含 "debug" 的行 sed '/debug/d' file.txt # 删除注释行(以 # 开头的行) sed '/^#/d' config.ini # 删除第 3 行到最后一行 sed '3,$d' file.txt # 删除所有行(清空文件效果,等价于输出空) sed '1,$d' file.txt # 删除空行和只含空白字符的行 sed '/^[[:space:]]*$/d' file.txt # 删除 HTML 标签(简化版) sed 's/<[^>]*>//g' file.html

3.3 替换(s 命令)

s 命令是 sed 中最常用的命令,用于在行内进行字符串替换。由于替换操作的复杂性和灵活性,第五章会详细展开,这里先介绍基本用法。

# 基本替换(每行只替换第一处匹配) sed 's/old/new/' file.txt # 全局替换(替换行内所有匹配) sed 's/old/new/g' file.txt # 只替换第 2 处匹配 sed 's/old/new/2' file.txt # 在第 3-10 行范围内进行替换 sed '3,10s/old/new/g' file.txt # 只打印被修改的行 sed -n 's/old/new/p' file.txt # 删除匹配的行(将替换为空字符串) sed 's/^#.*$//' config.ini # 注释行变成空行

3.4 插入(i 命令)和追加(a 命令)

i 命令在匹配行的前面插入文本,a 命令在匹配行的后面追加文本。

# 在第 3 行之前插入一行 sed '3i\这是新插入的行' file.txt # 在第 3 行之后追加一行 sed '3a\这是新追加的行' file.txt # 在匹配 "header" 的行之前插入 sed '/header/i\' file.html # 在文件末尾追加内容 sed '$a\# 这是文件末尾追加的内容' file.txt # 在匹配行后插入多行(使用 \n 分隔) sed '/main/a\行1\ 行2\ 行3' file.txt # 每行后面都插入一个空行 sed 'G' file.txt

3.5 修改(c 命令)

c 命令将匹配的行替换为指定的文本。

# 将第 3 行替换为指定内容 sed '3c\这是替换后的内容' file.txt # 将匹配的行替换 sed '/TODO/c\' file.txt # 将多行替换为一行(注意:c 命令会用新文本替换整个行范围) sed '2,4c\被替换的 2-4 行' file.txt

3.6 读取(r 命令)和写入(w 命令)

# 将文件内容读入到匹配行之后 sed '3r insert.txt' file.txt # 在第 3 行后插入 insert.txt 的内容 sed '$r footer.html' file.html # 在文件末尾插入 footer.html # 将匹配的行写入另一个文件 sed -n '/ERROR/w errors.txt' log.txt # 将包含 ERROR 的行写入 errors.txt sed -n '10,20w excerpt.txt' file.txt # 将 10-20 行写入 excerpt.txt

3.7 退出(q 命令)

# 读取前 10 行后退出(高效地读取文件头部) sed '10q' large_file.txt # 找到第一个 ERROR 后退出 sed '/ERROR/q' log.txt # 匹配后退出并输出自定义消息 sed '/ERROR/{s/.*/找到第一个 ERROR,退出/;q}' log.txt
q 命令的效率优势:

使用 sed '10q' 读取大文件的前 10 行比 head -10 在某些场景下更快,因为 sed 在读取到第 10 行后会立即退出,不再继续读取文件的其他部分。这对于处理 GB 级别的大文件非常有用。

3.8 组合操作示例

# 打印但不输出匹配行(删除 + 打印的其他行) sed -n '/private/d;p' file.txt # 多处替换 sed -e 's/foo/bar/g' -e 's/abc/xyz/g' file.txt # 使用分号分隔多个命令 sed 's/foo/bar/g; s/abc/xyz/g' file.txt # 删除空白行并替换文本 sed '/^$/d; s/old/new/g' file.txt # 多个命令使用花括号分组 sed -n '/ERROR/{s/^/!!! /;p}' file.txt # 对匹配行加前缀并打印

四、正则表达式与模式匹配

4.1 sed 中的正则表达式类型

sed 支持两种类型的正则表达式:基本正则表达式(BRE)是默认使用的;扩展正则表达式(ERE)需要通过 -E 选项启用。

元字符BRE(基本正则)ERE(扩展正则,-E)
+(1次或多次)\+\{1,\}+
?(0次或1次)\?\{0,1\}?
{n,m}(区间量词)\{n,m\}{n,m}
()(分组捕获)\(\)()
|(逻辑或)\||

推荐使用 -E 选项

在 GNU sed 4.2+ 中,推荐使用 -E 选项启用扩展正则表达式。相比 BRE,ERE 减少了大量的反斜杠转义,代码更清晰易读。在所有新编写的 sed 命令中,建议默认加上 -E 选项。

4.2 基本正则表达式的匹配

# 行首匹配 sed -n '/^#/p' config.ini # 打印以 # 开头的行 # 行尾匹配 sed -n '/\.$/p' file.txt # 打印以 . 结尾的行 # 任意字符匹配(. 匹配任意单个字符) sed -n '/c.t/p' file.txt # 匹配 cat, cut, c0t 等 # 字符集匹配 sed -n '/[0-9]/p' file.txt # 包含数字的行 sed -n '/[A-Za-z]/p' file.txt # 包含字母的行 sed -n '/[^0-9]/p' file.txt # 包含非数字字符的行 # 重复匹配 sed -n '/ab*c/p' file.txt # 匹配 ac, abc, abbc, abbbc... sed -n '/ab\+c/p' file.txt # GNU BRE: 匹配 abc, abbc...

4.3 扩展正则表达式的匹配

# 使用 -E 启用扩展正则 # + 量词:匹配一次或多次 sed -E -n '/ab+c/p' file.txt # 匹配 abc, abbc, abbbc... # ? 量词:匹配零次或一次 sed -E -n '/colou?r/p' file.txt # 匹配 color 和 colour # 区间量词 sed -E -n '/[0-9]{3,5}/p' file.txt # 匹配 3 到 5 位数字 sed -E -n '/^.{10,20}$/p' file.txt # 匹配 10-20 个字符的行 # 分组捕获 sed -E -n '/(ab)+/p' file.txt # 匹配 ab, abab, ababab... # 逻辑或 sed -E -n '/error|warning|fatal/p' log.txt # 匹配多种日志级别 # 单词边界(GNU sed 扩展) sed -E -n '/\bword\b/p' file.txt # 匹配完整单词 word

4.4 正则表达式替换实战

# 删除行首空白 sed -E 's/^[[:space:]]+//' file.txt # 删除行尾空白 sed -E 's/[[:space:]]+$//' file.txt # 同时清除行首行尾空白 sed -E 's/^[[:space:]]+|[[:space:]]+$//g' file.txt # 将多个连续空格合并为一个 sed -E 's/[[:space:]]+/ /g' file.txt # 邮箱地址掩码(保留域名) sed -E 's/\b([a-zA-Z0-9._%+-]+)@/\1\@/' emails.txt # IP 地址掩码(最后一段替换为 ***) sed -E 's/([0-9]{1,3}\.){3}[0-9]{1,3}/\1***/' access.log # 将 Markdown 标题转换为 HTML sed -E 's/^# (.*)/

\1<\/h1>/' file.md sed -E 's/^## (.*)/

\1<\/h2>/' file.md sed -E 's/^### (.*)/

\1<\/h3>/' file.md # 将 Markdown 链接转换为 HTML sed -E 's/\[([^\]]+)\]\(([^)]+)\)/\1<\/a>/g' file.md

4.5 POSIX 字符类

sed 支持 POSIX 字符类,这些类在各种语言环境中使用,比字符集 [a-z] 更可靠。

字符类说明等价写法
[[:alnum:]]字母和数字[a-zA-Z0-9]
[[:alpha:]]字母[a-zA-Z]
[[:digit:]]数字[0-9]
[[:lower:]]小写字母[a-z]
[[:upper:]]大写字母[A-Z]
[[:space:]]空白字符空格、Tab、换行等
[[:punct:]]标点符号[.,!?:;...]
[[:blank:]]空格和 Tab[ \t]
[[:print:]]可打印字符[ -~]
# 使用 POSIX 字符类的示例 sed -E 's/[[:digit:]]+//g' file.txt # 将所有数字替换为 sed -E 's/[[:punct:]]+/ /g' file.txt # 将所有标点替换为空格 sed -E 's/[[:upper:]]/\L&/g' file.txt # 将所有大写转为小写(GNU) sed -E '/^[[:space:]]*$/d' file.txt # 删除空白行和只含空格的行

五、替换操作详解

5.1 s 命令完整语法

s 命令是 sed 中使用频率最高的命令,其完整语法为:

[地址范围]s/模式/替换/[标志] # 结构说明 # 地址范围:可选,指定哪些行执行替换(如 3,5 或 /pattern/) # s :替换命令 # / :分隔符(可用其他字符替代) # 模式 :要搜索的正则表达式模式 # 替换 :替换文本(可包含反向引用) # 标志 :控制替换行为(如 g, p, w, i, 数字)

5.2 替换分隔符的选择

默认分隔符是 /,但当模式或替换文本中包含斜杠时,可以使用其他字符作为分隔符,提高可读性。

# 默认 / 分隔符(路径替换时需要转义) sed 's/\/usr\/local\/bin/\/usr\/bin/g' file.txt # 使用 # 作为分隔符(更清晰) sed 's#/usr/local/bin#/usr/bin#g' file.txt # 使用 | 作为分隔符 sed 's|/usr/local/bin|/usr/bin|g' file.txt # 使用 @ 作为分隔符(处理 URL 时很合适) sed 's@http://example.com@https://example.com@g' file.txt # 使用 _ 作为分隔符 sed 's_old_value_new_value_g' file.txt
分隔符选择原则:

选择替换模式中不会出现的字符作为分隔符。如果替换路径,用 #|;替换 URL,用 @。这样可以完全避免转义,让代码更清晰。

5.3 替换标志详解

标志含义示例说明
g全局替换s/old/new/g替换行内所有匹配(默认只替换第一个)
N第 N 次匹配s/old/new/2只替换行内的第 N 次匹配
p打印s/old/new/p仅打印成功替换的行(需配合 -n)
w写入文件s/old/new/w out.txt将替换成功的行写入文件
i忽略大小写s/old/new/iGNU sed 支持,不区分大小写
e执行命令s/.*/echo &/eGNU sed 扩展,将替换结果作为命令执行
# 不同标志的用法对比 sed 's/foo/bar/' file.txt # 默认:每行只替换第一个 foo sed 's/foo/bar/g' file.txt # g:替换行内所有 foo sed 's/foo/bar/2' file.txt # 2:只替换每行第二个 foo sed -n 's/foo/bar/p' file.txt # p:只输出发生替换的行 sed -n 's/foo/bar/w out.txt' file.txt # w:将替换行写入文件 sed -n 's/foo/bar/ip' file.txt # i:忽略大小写匹配并打印结果 sed 's/foo/bar/gi' file.txt # gi:全局且忽略大小写 sed 's/foo/bar/2g' file.txt # 2g:从第 2 个匹配开始全部替换

5.4 反向引用

反向引用允许在替换文本中引用模式中捕获组匹配的内容。在 BRE 中使用 \(\) 捕获,\1\9 引用;在 ERE 中使用 () 捕获。

# 基本反向引用 # 交换第一列和第二列(以 : 分隔) sed -E 's/^([^:]+):(.*)/\2:\1/' file.txt # 将名字从 "姓, 名" 格式转为 "名 姓" echo "张, 三" | sed -E 's/([^,]+), (.+)/\2 \1/' # 输出:三 张 # 给数字添加千位分隔符 echo "1234567" | sed -E 's/([0-9])([0-9]{3})$/\1,\2/' # 输出:1234,567 # 更完整的千位分隔符(递归替换) echo "1234567890" | sed -E ':a; s/([0-9]+)([0-9]{3})/\1,\2/; ta' # 输出:1,234,567,890 # 引用整个匹配(& 符号) sed 's/[0-9]\+/(&)/g' file.txt # 将每个数字用括号包裹 sed 's/[A-Z]\+/**&**/g' file.txt # 将大写字母加粗标记 # 多组反向引用 # 将日期格式从 YYYY-MM-DD 转为 DD/MM/YYYY echo "2024-03-15" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/' # 输出:15/03/2024 # 使用反向引用进行重复词检测 sed -E 's/\b([A-Za-z]+) \1\b/\1/g' file.txt # 删除重复的词

5.5 替换中的转义序列

在替换文本中,可以使用一些特殊转义序列(GNU sed 支持):

序列含义说明
\1\9反向引用引用第 1 到第 9 个捕获组
\&整个匹配引用整个正则表达式的匹配内容
\U转大写将后续字符转换为大写
\L转小写将后续字符转换为小写
\E结束转换停止 \U 或 \L 的大小写转换
\u首字母大写将下一个字符转为大写
\l首字母小写将下一个字符转为小写
# 大小写转换示例(GNU sed) echo "hello world" | sed 's/.*/\U&/' # HELLO WORLD echo "HELLO WORLD" | sed 's/.*/\L&/' # hello world echo "hello world" | sed 's/\b\w/\u&/g' # Hello World(标题化) echo "user_name" | sed -E 's/_([a-z])/\u\1/g' # userName(驼峰转换) # 综合:将 CSS 属性转为驼峰 echo "background-color" | sed -E 's/-([a-z])/\u\1/g' # 输出:backgroundColor

5.6 实战:批量替换配置文件的典型场景

# 场景 1:修改 Apache/Nginx 配置文件 sed -i 's/Listen 80/Listen 8080/' /etc/apache2/ports.conf # 场景 2:批量修改数据库连接配置 sed -i 's|DB_HOST=localhost|DB_HOST=db.example.com|g' .env sed -i 's|DB_PASSWORD=oldpass|DB_PASSWORD=newpass|g' .env # 场景 3:更新版本号 sed -i 's/version = "1\.0\.0"/version = "1.0.1"/' setup.py # 场景 4:注释掉调试日志 sed -i '/^debug:/s/^debug:/# debug:/' config.yml # 场景 5:给配置文件中的值加引号 sed -E 's/^([^=]+)=([^"].*[^"])$/\1="\2"/' config.ini # 场景 6:批量替换文件名中的空格 for f in *.txt; do mv "$f" "$(echo "$f" | sed 's/ /_/g')" done

六、地址范围与行选择

6.1 地址类型概览

地址(address)用于指定 sed 命令作用于哪些行。地址可以是行号、正则表达式或特殊符号。

地址类型语法示例含义
数字行号N3d只对第 3 行执行
行号范围N,M3,6d对第 3 到第 6 行执行
步进模式N~M1~2p从第 N 行开始,每 M 行执行一次
正则模式/regex//^$/d匹配正则表达式的行
最后一行$$d最后一行
取反!3!d除了第 3 行以外的所有行

6.2 行号模式详解

# 绝对行号 sed -n '1p' file.txt # 第 1 行 sed -n '5p' file.txt # 第 5 行 # 行号范围 sed -n '5,10p' file.txt # 第 5 到 10 行 sed '5,10d' file.txt # 删除第 5 到 10 行 sed '5,10s/foo/bar/g' file.txt # 在第 5 到 10 行替换 # 从某行到最后 sed -n '100,$p' file.txt # 从第 100 行到末尾 sed '100,$d' file.txt # 删除第 100 行到末尾 # 步进模式(GNU sed) sed -n '1~2p' file.txt # 奇数行(从第 1 行开始,每 2 行一次) sed -n '2~2p' file.txt # 偶数行(从第 2 行开始,每 2 行一次) sed -n '3~5p' file.txt # 第 3, 8, 13, 18... 行 # 取反操作 sed -n '5!p' file.txt # 除了第 5 行,打印其他所有行 sed '5,10!d' file.txt # 保留第 5-10 行,删除其他 sed '/^#/!d' file.txt # 只保留非注释行

6.3 正则模式匹配地址

# 单模式匹配 sed -n '/ERROR/p' log.txt # 包含 ERROR 的行 sed '/^$/d' file.txt # 删除空行 # 模式范围(从匹配第一个模式到匹配第二个模式之间的行) sed -n '//,/<\/body>/p' index.html # 提取 body 标签内容 sed -n '/START/,/END/p' file.txt # 提取 START 到 END 之间的行 # 模式范围的变体 sed -n '/ERROR/,+5p' log.txt # 匹配 ERROR 行及其后 5 行 sed -n '/ERROR/,/^$/p' log.txt # 从 ERROR 行到下一个空行 sed '/BEGIN/,/END/!d' file.txt # 保留 BEGIN 到 END 的行(取反删除其他) # 多个模式 sed -e '/pattern1/d' -e '/pattern2/d' file.txt # 删除匹配任一模式的行 sed '/pattern1/{/pattern2/d}' file.txt # 删除同时匹配两个模式的行

模式范围的工作原理

当 sed 遇到形如 /addr1/,/addr2/ 的地址范围时,它会从匹配 addr1 的第一行开始激活命令,一直持续到匹配 addr2 的行为止。如果文件中有多个这样的范围,每个范围都会被处理。这种机制非常适合提取日志中的异常堆栈、配置文件块或者 HTML 片段。

6.4 地址范围与替换的组合

# 只在前 10 行替换 sed '1,10s/old/new/g' file.txt # 在匹配的章节内替换 sed '/\[database\]/,/\[/s/host/localhost/g' config.ini # 排除空行处理 sed '/^$/!s/old/new/g' file.txt # 非空行执行替换 # 在匹配行到文件末尾之间执行替换 sed '/^# Custom Settings/,$s/^# //' config.ini # 从第 10 行开始,每隔 3 行执行一次替换 sed '10~3s/foo/bar/g' file.txt

6.5 练习案例:处理配置文件

# 假设有以下配置文件 config.ini: # [database] # host = old_host # port = 5432 # # [cache] # host = old_host # port = 6379 # 需求:只修改 [database] 节的 host,不改 [cache] 的 host sed '/\[database\]/,/^\[/{s/host = .*/host = new_host/}' config.ini # 需求:在 [database] 节后面追加一行 sed '/\[database\]/a\timeout = 30' config.ini # 需求:删除 [cache] 节的所有内容 sed '/\[cache\]/,/^\[/{/^\[/!d}' config.ini

七、高级命令(分支、测试、标签)

7.1 标签与分支(b 命令)

sed 支持类似汇编语言的标签和分支机制,可以实现条件执行和循环处理。

# 标签和分支的基本语法 sed ':label 命令序列 b label' file.txt # 示例:使用分支跳过特定行 # 跳过注释行,对其他行执行替换 sed '/^#/b; s/foo/bar/g' file.txt # 使用标签实现条件跳转 # 如果行以 "START" 开头,跳转到输出阶段 sed '/^START/b output; s/.*/prefix_&/; :output' file.txt # 无条件分支到脚本末尾(相当于跳过后续所有命令) sed '/^#/b end; s/foo/bar/; :end' file.txt

7.2 测试命令(t 命令)

t 命令在最近一次替换成功时跳转到指定的标签。这是实现循环替换的关键命令。

# 测试命令的基本语法 sed ':label s/模式/替换/ t label' file.txt # 示例:递归替换直到没有匹配 # 将多个连续空格逐步合并为单个空格 sed ':a; s/ / /g; ta' file.txt # 更实用:添加千位分隔符 echo "1234567890" | sed ':a; s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/; ta' # 输出:1,234,567,890 # 使用 -E 的简洁版本 echo "1234567890" | sed -E ':a; s/([0-9]+)([0-9]{3})/\1,\2/; ta' # 输出:1,234,567,890 # 条件替换:第一次替换后跳出 sed ':a; s/old/new/; ta; s/another/different/' file.txt
b vs t 的区别:

b(branch)是无条件分支,遇到就跳转。而 t(test)是有条件分支,只有当前行上一次 s 替换操作成功时才跳转。通常 t 用于实现递归替换(循环直到没有可替换的内容),b 用于条件判断和跳过逻辑。

7.3 替换命令(y 命令)

y 命令用于字符级替换(类似 tr 命令),将一组字符逐一映射到另一组字符。

# 将小写字母映射为大写字母 sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' file.txt # 等效于 tr 命令: tr 'a-z' 'A-Z' < file.txt # 将 [, ] 替换为 ( , ) sed 'y/[]/()/' file.txt # 注意:y 命令的源和目的字符集长度必须相同,且不支持正则 # 错误示例:sed 'y/[a-z]/[A-Z]/' file.txt # 不会达到预期效果 # 将制表符替换为 4 个空格(用 y 只能替换为 1 个字符) sed 'y/\t/ /' file.txt # Tab 转空格 # 移除 Windows 换行符(\r\n -> \n) sed 'y/\r//' file.txt # 删除 \r

7.4 其他高级命令

# = 命令:输出行号 sed -n '/ERROR/=' log.txt # 输出包含 ERROR 的行号 sed -n '/^$/=; p' file.txt # 打印空行的行号 # l 命令:显示不可打印字符(类似 cat -A) sed -n 'l' file.txt # 显示 Tab 为 \t,行尾为 $ # n 命令:读取下一行到模式空间 sed '/pattern/{n; s/foo/bar/;}' file.txt # 匹配 pattern 后,读取下一行并替换 # N 命令:将下一行追加到模式空间(多行处理,见第八章) sed 'N; s/\n/ /' file.txt # 合并两行 # q 命令:退出(可以在满足条件时停止处理) sed '/ERROR/q' log.txt # 遇到第一个 ERROR 行时退出 sed '100q' bigfile.txt # 处理完 100 行后退出

7.5 实战:电话号码格式化

# 将 13812345678 格式化为 138-1234-5678 echo "13812345678" | sed -E 's/([0-9]{3})([0-9]{4})([0-9]{4})/\1-\2-\3/' # 输出:138-1234-5678 # 处理更多格式(使用循环处理多种格式) echo "13812345678" | sed -E ':a; s/([0-9]+)([0-9]{4})/\1-\2/; ta' # 先变成 1381234-5678,再变成 138-1234-5678 # 批量格式化文件中的电话号码 sed -E 's/(1[3-9][0-9])([0-9]{4})([0-9]{4})/\1-\2-\3/g' contacts.txt

八、多行模式处理

8.1 模式空间与保持空间概述

sed 有两个内部存储缓冲区:模式空间(pattern space)和保持空间(hold space)。模式空间是主工作区,每一行文本都会先被读入模式空间。保持空间是一个辅助缓冲区,可以暂存数据以便后续处理。

命令全称作用
hhold将模式空间的内容复制到保持空间(覆盖)
HHold将模式空间的内容追加到保持空间
gget将保持空间的内容复制到模式空间(覆盖)
GGet将保持空间的内容追加到模式空间
xexchange交换模式空间和保持空间的内容
nnext读取下一行到模式空间(覆盖当前内容)
NNext将下一行追加到模式空间(用 \n 分隔)
PPrint打印模式空间的第一行(到第一个 \n 为止)
DDelete删除模式空间的第一行(到第一个 \n 为止)

8.2 N 命令:多行合并处理

N 命令将下一行读取并追加到当前模式空间中,中间用换行符 \n 分隔。这允许跨行匹配模式。

# 合并奇数行和偶数行 sed 'N; s/\n/ /' file.txt # 将两行合并为一行 # 合并段落(将连续的行合并为一行) sed ':a; N; $!ba; s/\n/ /g' file.txt # 将整个文件合并为一行 # 跨行搜索并替换 # 将 "hello\nworld" 替换为 "hello world" sed '/hello/{ N; s/hello\nworld/hello world/; }' file.txt # 在匹配行和下一行之间插入空行 sed '/pattern/{ N; s/\n/\n\n/; }' file.txt # 修复被分割的句子 # 将以 , 结尾的行与下一行合并 sed '/,$/{ N; s/,\n/, /; }' file.txt

8.3 保持空间的应用

保持空间最常见的用途是实现"颠倒输出"和"累积匹配行"。

# 倒序输出文件(类似 tac 命令) sed '1!G; h; $!d' file.txt # 解释: # 1!G :除了第 1 行外,将保持空间内容追加到模式空间 # h :将当前模式空间内容复制到保持空间 # $!d :除了最后一行外,删除模式空间 # 只输出最后 10 行(类似于 tail -10) sed '${p; :a; N; /^\n.*\n.*\n.*\n.*\n.*\n.*\n.*\n.*\n.*\n$/!ba; s/.*\n//; p}' file.txt # 更简单的方式:用保持空间实现 sed 'H; $!d; ${ x; s/^\n//; }' file.txt | tail -10 # 累积匹配行 sed -n '/ERROR/{ H; }; ${ x; s/^\n//; p; }' log.txt # 将所有的 ERROR 行收集到保持空间,最后一起输出 # 并行文件处理:交错合并两个文件的行 # sed 本身不支持直接处理两个文件,但可以用管道组合 paste -d '\n' file1.txt file2.txt | sed 'N; s/\n/ | /'

理解 sed 的"颠倒"过程

sed '1!G; h; $!d' 这个看似神秘的命令是这样工作的:

  1. 读取第 1 行时:跳过 G,用 h 保存到保持空间,然后 d 删除(不输出)。保持空间现在有第 1 行。
  2. 读取第 2 行时:执行 G(将保持空间内容追加到模式空间,模式空间变为"第2行\n第1行"),h 将合并结果保存到保持空间,d 删除。保持空间有"第2行\n第1行"。
  3. 继续处理,保持空间永远存储着"当前行\n之前的所有行"。
  4. 最后一行($)时:不执行 d,模式空间的内容("最后一行\n...\n第一行")被自动输出。

8.4 D 和 P 命令:部分操作

DP 命令只处理模式空间中的第一行(第一个换行符之前的内容),常与 N 命令配合。

# D 命令:删除模式空间的第一行 sed 'N; /pattern/{ D; }; P; D' file.txt # 实现:只保留匹配行之后的 N 行 sed '/PATTERN/!d; N; N; N; /PATTERN.*\n.*\n.*\n$/P; D' file.txt # P 命令:只打印模式空间的第一行 sed -n 'N; P' file.txt # 只打印奇数行(读取两行,只输出第一行) # 使用 D 和 P 实现循环读取段落 sed '/^$/!{ H; d; }; x; s/^\n//; /pattern/p' file.txt # 将段落累积到保持空间,遇到空行时输出包含 pattern 的段落

8.5 实战:提取特定段落

# 提取包含 "ERROR" 的日志段落(假设段落用空行分隔) sed -n '/^$/!{ H; d; }; x; s/^\n//; /ERROR/p; s/.*//; h' log.txt # 实现说明: # 1. 如果当前行非空(/^$/!),追加到保持空间并删除模式空间 # 2. 如果当前行是空行(/^$/ 匹配),交换保持空间到模式空间 # 3. 去掉开头的换行符(s/^\n//) # 4. 如果段落包含 ERROR 则打印 # 5. 清空保持空间,为下一个段落做准备 # 提取两个标记之间的所有行 sed -n '//,/<\/chapter>/p' book.xml # 提取并合并连续的日志行(直到遇到空行) sed -n '/^[^$]/{ H; }; /^$/{ x; s/^\n//; p; s/.*//; h; }' log.txt

九、脚本化与实用技巧

9.1 使用 sed 脚本文件

当 sed 命令非常复杂时,可以将其写入脚本文件,通过 -f 选项调用。

# 创建 sed 脚本文件 process.sed: # 内容如下: # 删除注释行 # /^#/d # 删除空行 # /^$/d # 将 "old" 替换为 "new" # s/old/new/g # 使用脚本文件 sed -f process.sed input.txt > output.txt # 多命令脚本可以写为: # --- 文件 clean_config.sed --- # # 清理配置文件脚本 # /^[[:space:]]*$/d # 删除空行 # /^[[:space:]]*#/d # 删除注释行 # s/[[:space:]]\+$// # 删除行尾空白 # s/^[[:space:]]\+// # 删除行首空白 # s/ = /=/g # 标准化等号格式 # 应用脚本 sed -f clean_config.sed config.ini

9.2 管道中的 sed

sed 经常在管道中与其他命令组合使用,形成强大的数据处理流水线。

# 经典组合:grep + sed + sort grep -rn "TODO" src/ | sed 's/:/ | /' | sort # 提取 CPU 温度并格式化 cat /sys/class/thermal/thermal_zone*/temp | sed 's/\(...\)$/.&°C/' | head -1 # 日志分析流水线 cat access.log | sed -n '/5[0-9][0-9]/p' | # 筛选 5xx 错误 sed -E 's/.* ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) .*/\1/' | # 提取 IP sort | uniq -c | sort -rn | # 统计 IP 出现次数 head -10 # 取前 10 # 与 find 组合:批量替换多个文件 find . -name "*.html" -exec sed -i 's/old.css/new.css/g' {} \; # 与 xargs 组合:大规模替换 grep -rl "old_text" --include="*.py" . | xargs sed -i 's/old_text/new_text/g' # 从命令输出中提取信息 ifconfig | sed -n '/inet /s/.*inet \([0-9.]*\).*/\1/p' # 提取本机 IP 地址

9.3 sed 实用工具函数

以下是一些可以放入 Shell 脚本中的 sed 工具函数:

# 函数:删除配置文件中的注释和空行 clean_config() { sed -E '/^[[:space:]]*(#|$)/d' "$1" } # 函数:给每行添加行号 number_lines() { sed -n '/./=' "$1" | sed 'N; s/\n/ /' } # 或者更简单的: number_lines_v2() { sed -n '$!N; s/^/& /' "$1" } # 函数:将 Tab 转为空格 tab_to_space() { sed 's/\t/ /g' "$1" } # 函数:移除行尾空白 trim_trailing() { sed -i 's/[[:space:]]*$//' "$1" } # 函数:给 Markdown 标题添加编号 number_headers() { local count=0 sed -E '/^## /{ s/^## /&'$((++count))'. /; }' "$1" } # 函数:提取两行之间的内容(含边界标记) extract_between() { sed -n "/$1/,/$2/p" "$3" } # 用法示例 # extract_between "" "" index.html # clean_config ~/.ssh/config

9.4 高效技巧集锦

# 1. 用 sed 生成 CSS 类名字典 sed -n 's/.*\.\([a-zA-Z-]*\).*/\1/p' style.css | sort -u # 2. 统计每行字数 sed 's/[^ ]/o/g' file.txt | sed 's/ //g;s/o/./g' | sed -n 's/./&/gp' | wc -c # 3. CSV 文件字段提取:提取第 2 和第 5 列 sed -E 's/^([^,]*),([^,]*),([^,]*),([^,]*),(.*)/\2 \5/' data.csv # 4. 将 JSON 键转换为小写 sed -E 's/"([A-Z][^"]*)":/\L\1:/g' data.json # 5. 给文件的每 5 行插入一个分隔线 sed '5~5i\----------------------------------------' file.txt # 6. 快速创建 HTML 列表 sed 's/.*/
  • &<\/li>/' items.txt > list.html sed '1i\
      ' list.html > list2.html sed '$a\<\/ul>' list2.html > final.html # 7. 将错误输出和标准输出合并重定向 sed 's/pattern/replacement/' file.txt 2>&1 # 8. 从 PATH 中提取每个目录(分行显示) echo "$PATH" | sed 'y/:/\n/' # 9. 删除 HTML 文件中所有 script 标签内容 sed '/